-# Check if lld is built as a standalone project.
+cmake_minimum_required(VERSION 3.13.4)
+
+if(NOT DEFINED LLVM_COMMON_CMAKE_UTILS)
+ set(LLVM_COMMON_CMAKE_UTILS ${CMAKE_CURRENT_SOURCE_DIR}/../cmake)
+endif()
+include(${LLVM_COMMON_CMAKE_UTILS}/Modules/CMakePolicy.cmake
+ NO_POLICY_SCOPE)
+
+# If we are not building as a part of LLVM, build LLD as an
+# standalone project, using LLVM as an external library:
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
project(lld)
- cmake_minimum_required(VERSION 3.13.4)
-
- set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(LLD_BUILT_STANDALONE TRUE)
-
- find_program(LLVM_CONFIG_PATH "llvm-config" DOC "Path to llvm-config binary")
- if(NOT LLVM_CONFIG_PATH)
- message(FATAL_ERROR "llvm-config not found: specify LLVM_CONFIG_PATH")
- endif()
-
- execute_process(COMMAND "${LLVM_CONFIG_PATH}"
- "--obj-root"
- "--includedir"
- "--cmakedir"
- "--src-root"
- RESULT_VARIABLE HAD_ERROR
- OUTPUT_VARIABLE LLVM_CONFIG_OUTPUT
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- if(HAD_ERROR)
- message(FATAL_ERROR "llvm-config failed with status ${HAD_ERROR}")
+ if ("${CMAKE_VERSION}" VERSION_LESS "3.20.0")
+ message(WARNING
+ "Your CMake version is ${CMAKE_VERSION}. Starting with LLVM 17.0.0, the "
+ "minimum version of CMake required to build LLVM will become 3.20.0, and "
+ "using an older CMake will become an error. Please upgrade your CMake to "
+ "at least 3.20.0 now to avoid issues in the future!")
endif()
+endif()
- string(REGEX REPLACE "[ \t]*[\r\n]+[ \t]*" ";" LLVM_CONFIG_OUTPUT "${LLVM_CONFIG_OUTPUT}")
+# Must go below project(..)
+include(GNUInstallDirs)
- list(GET LLVM_CONFIG_OUTPUT 0 OBJ_ROOT)
- list(GET LLVM_CONFIG_OUTPUT 1 MAIN_INCLUDE_DIR)
- list(GET LLVM_CONFIG_OUTPUT 2 LLVM_CMAKE_PATH)
- list(GET LLVM_CONFIG_OUTPUT 3 MAIN_SRC_DIR)
+if(LLD_BUILT_STANDALONE)
+ set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to conform to")
+ set(CMAKE_CXX_STANDARD_REQUIRED YES)
+ set(CMAKE_CXX_EXTENSIONS NO)
- set(LLVM_OBJ_ROOT ${OBJ_ROOT} CACHE PATH "path to LLVM build tree")
- set(LLVM_MAIN_INCLUDE_DIR ${MAIN_INCLUDE_DIR} CACHE PATH "path to llvm/include")
- set(LLVM_MAIN_SRC_DIR ${MAIN_SRC_DIR} CACHE PATH "Path to LLVM source tree")
+ set(CMAKE_INCLUDE_CURRENT_DIR ON)
- file(TO_CMAKE_PATH ${LLVM_OBJ_ROOT} LLVM_BINARY_DIR)
- file(TO_CMAKE_PATH ${LLVM_CMAKE_PATH} LLVM_CMAKE_PATH)
+ find_package(LLVM REQUIRED HINTS "${LLVM_CMAKE_DIR}")
+ list(APPEND CMAKE_MODULE_PATH "${LLVM_DIR}")
- if(NOT EXISTS "${LLVM_CMAKE_PATH}/LLVMConfig.cmake")
- message(FATAL_ERROR "LLVMConfig.cmake not found")
- endif()
- include("${LLVM_CMAKE_PATH}/LLVMConfig.cmake")
+ # Turn into CACHE PATHs for overwriting
+ set(LLVM_INCLUDE_DIRS ${LLVM_INCLUDE_DIRS} CACHE PATH "Path to llvm/include and any other header dirs needed")
+ set(LLVM_BINARY_DIR "${LLVM_BINARY_DIR}" CACHE PATH "Path to LLVM build tree")
+ set(LLVM_MAIN_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../llvm" CACHE PATH "Path to LLVM source tree")
- list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_PATH}")
+ find_program(LLVM_TABLEGEN_EXE "llvm-tblgen" ${LLVM_TOOLS_BINARY_DIR}
+ NO_DEFAULT_PATH)
- set(PACKAGE_VERSION "${LLVM_PACKAGE_VERSION}")
- include_directories("${LLVM_BINARY_DIR}/include" ${LLVM_INCLUDE_DIRS})
- link_directories(${LLVM_LIBRARY_DIRS})
-
- set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib${LLVM_LIBDIR_SUFFIX})
+ # They are used as destination of target generators.
set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin)
- find_program(LLVM_TABLEGEN_EXE "llvm-tblgen" ${LLVM_TOOLS_BINARY_DIR} NO_DEFAULT_PATH)
+ set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib${LLVM_LIBDIR_SUFFIX})
include(AddLLVM)
include(TableGen)
include(GetErrcMessages)
include(CheckAtomic)
+ set(PACKAGE_VERSION "${LLVM_PACKAGE_VERSION}")
+
+ include_directories(${LLVM_INCLUDE_DIRS})
+ link_directories(${LLVM_LIBRARY_DIRS})
+
if(LLVM_INCLUDE_TESTS)
find_package(Python3 ${LLVM_MINIMUM_PYTHON_VERSION} REQUIRED
COMPONENTS Interpreter)
set(LLVM_UTILS_PROVIDED ON)
set(LLD_TEST_DEPS FileCheck not)
endif()
- set(UNITTEST_DIR ${LLVM_MAIN_SRC_DIR}/utils/unittest)
+ set(UNITTEST_DIR ${LLVM_THIRD_PARTY_DIR}/unittest)
if(EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h
AND NOT EXISTS ${LLVM_LIBRARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}
AND EXISTS ${UNITTEST_DIR}/CMakeLists.txt)
- add_subdirectory(${UNITTEST_DIR} utils/unittest)
+ add_subdirectory(${UNITTEST_DIR} third-party/unittest)
endif()
else()
# Seek installed Lit.
if(LLVM_HAVE_LIBXAR)
set(XAR_LIB xar)
endif()
-endif()
+endif() # standalone
+
+set(LLD_TOOLS_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}" CACHE PATH
+ "Path for binary subdirectory (defaults to '${CMAKE_INSTALL_BINDIR}')")
+mark_as_advanced(LLD_TOOLS_INSTALL_DIR)
set(LLD_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(LLD_INCLUDE_DIR ${LLD_SOURCE_DIR}/include )
"`CMakeFiles'. Please delete them.")
endif()
-list (APPEND CMAKE_MODULE_PATH "${LLD_SOURCE_DIR}/cmake/modules")
+# Add path for custom modules.
+list(INSERT CMAKE_MODULE_PATH 0
+ "${LLD_SOURCE_DIR}/cmake/modules"
+ "${LLVM_COMMON_CMAKE_UTILS}/Modules"
+ )
include(AddLLD)
if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
install(DIRECTORY include/
- DESTINATION include
+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
FILES_MATCHING
PATTERN "*.h"
)
endif()
add_subdirectory(Common)
-add_subdirectory(lib)
add_subdirectory(tools/lld)
if (LLVM_INCLUDE_TESTS)
add_subdirectory(test)
- add_subdirectory(unittests)
endif()
add_subdirectory(docs)
add_lld_library(lldCOFF
CallGraphSort.cpp
Chunks.cpp
+ COFFLinkerContext.cpp
DebugTypes.cpp
DLL.cpp
Driver.cpp
Option
Passes
Support
+ TargetParser
+ WindowsDriver
WindowsManifest
LINK_LIBS
lldCommon
${LLVM_PTHREAD_LIB}
+ ${LLVM_ATOMIC_LIB}
DEPENDS
COFFOptionsTableGen
--- /dev/null
+//===- COFFContext.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Description
+//
+//===----------------------------------------------------------------------===//
+
+#include "COFFLinkerContext.h"
+#include "Symbols.h"
+#include "lld/Common/Memory.h"
+#include "llvm/BinaryFormat/COFF.h"
+#include "llvm/DebugInfo/CodeView/TypeHashing.h"
+#include "llvm/Demangle/Demangle.h"
+
+namespace lld::coff {
+COFFLinkerContext::COFFLinkerContext()
+ : driver(*this), symtab(*this),
+ ltoTextSection(llvm::COFF::IMAGE_SCN_MEM_EXECUTE),
+ ltoDataSection(llvm::COFF::IMAGE_SCN_CNT_INITIALIZED_DATA),
+ ltoTextSectionChunk(<oTextSection.section),
+ ltoDataSectionChunk(<oDataSection.section),
+ rootTimer("Total Linking Time"),
+ inputFileTimer("Input File Reading", rootTimer),
+ ltoTimer("LTO", rootTimer), gcTimer("GC", rootTimer),
+ icfTimer("ICF", rootTimer), codeLayoutTimer("Code Layout", rootTimer),
+ outputCommitTimer("Commit Output File", rootTimer),
+ totalMapTimer("MAP Emission (Cumulative)", rootTimer),
+ symbolGatherTimer("Gather Symbols", totalMapTimer),
+ symbolStringsTimer("Build Symbol Strings", totalMapTimer),
+ writeTimer("Write to File", totalMapTimer),
+ totalPdbLinkTimer("PDB Emission (Cumulative)", rootTimer),
+ addObjectsTimer("Add Objects", totalPdbLinkTimer),
+ typeMergingTimer("Type Merging", addObjectsTimer),
+ loadGHashTimer("Global Type Hashing", addObjectsTimer),
+ mergeGHashTimer("GHash Type Merging", addObjectsTimer),
+ symbolMergingTimer("Symbol Merging", addObjectsTimer),
+ publicsLayoutTimer("Publics Stream Layout", totalPdbLinkTimer),
+ tpiStreamLayoutTimer("TPI Stream Layout", totalPdbLinkTimer),
+ diskCommitTimer("Commit to Disk", totalPdbLinkTimer) {}
+} // namespace lld::coff
--- /dev/null
+//===- COFFLinkerContext.h --------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLD_COFF_COFFLINKERCONTEXT_H
+#define LLD_COFF_COFFLINKERCONTEXT_H
+
+#include "Chunks.h"
+#include "Config.h"
+#include "DebugTypes.h"
+#include "Driver.h"
+#include "InputFiles.h"
+#include "SymbolTable.h"
+#include "Writer.h"
+#include "lld/Common/CommonLinkerContext.h"
+#include "lld/Common/Timer.h"
+
+namespace lld::coff {
+
+class COFFLinkerContext : public CommonLinkerContext {
+public:
+ COFFLinkerContext();
+ COFFLinkerContext(const COFFLinkerContext &) = delete;
+ COFFLinkerContext &operator=(const COFFLinkerContext &) = delete;
+ ~COFFLinkerContext() = default;
+
+ LinkerDriver driver;
+ SymbolTable symtab;
+ COFFOptTable optTable;
+
+ std::vector<ObjFile *> objFileInstances;
+ std::map<std::string, PDBInputFile *> pdbInputFileInstances;
+ std::vector<ImportFile *> importFileInstances;
+ std::vector<BitcodeFile *> bitcodeFileInstances;
+
+ MergeChunk *mergeChunkInstances[Log2MaxSectionAlignment + 1] = {};
+
+ /// All sources of type information in the program.
+ std::vector<TpiSource *> tpiSourceList;
+
+ void addTpiSource(TpiSource *tpi) { tpiSourceList.push_back(tpi); }
+
+ std::map<llvm::codeview::GUID, TpiSource *> typeServerSourceMappings;
+ std::map<uint32_t, TpiSource *> precompSourceMappings;
+
+ /// List of all output sections. After output sections are finalized, this
+ /// can be indexed by getOutputSection.
+ std::vector<OutputSection *> outputSections;
+
+ OutputSection *getOutputSection(const Chunk *c) const {
+ return c->osidx == 0 ? nullptr : outputSections[c->osidx - 1];
+ }
+
+ // Fake sections for parsing bitcode files.
+ FakeSection ltoTextSection;
+ FakeSection ltoDataSection;
+ FakeSectionChunk ltoTextSectionChunk;
+ FakeSectionChunk ltoDataSectionChunk;
+
+ // All timers used in the COFF linker.
+ Timer rootTimer;
+ Timer inputFileTimer;
+ Timer ltoTimer;
+ Timer gcTimer;
+ Timer icfTimer;
+
+ // Writer timers.
+ Timer codeLayoutTimer;
+ Timer outputCommitTimer;
+ Timer totalMapTimer;
+ Timer symbolGatherTimer;
+ Timer symbolStringsTimer;
+ Timer writeTimer;
+
+ // PDB timers.
+ Timer totalPdbLinkTimer;
+ Timer addObjectsTimer;
+ Timer typeMergingTimer;
+ Timer loadGHashTimer;
+ Timer mergeGHashTimer;
+ Timer symbolMergingTimer;
+ Timer publicsLayoutTimer;
+ Timer tpiStreamLayoutTimer;
+ Timer diskCommitTimer;
+
+ Configuration config;
+};
+
+} // namespace lld::coff
+
+#endif
//===----------------------------------------------------------------------===//
#include "CallGraphSort.h"
+#include "COFFLinkerContext.h"
#include "InputFiles.h"
#include "SymbolTable.h"
#include "Symbols.h"
class CallGraphSort {
public:
- CallGraphSort();
+ CallGraphSort(const COFFLinkerContext &ctx);
DenseMap<const SectionChunk *, int> run();
private:
std::vector<Cluster> clusters;
std::vector<const SectionChunk *> sections;
+
+ const COFFLinkerContext &ctx;
};
// Maximum amount the combined cluster density can be worse than the original
// Take the edge list in Config->CallGraphProfile, resolve symbol names to
// Symbols, and generate a graph between InputSections with the provided
// weights.
-CallGraphSort::CallGraphSort() {
- MapVector<SectionPair, uint64_t> &profile = config->callGraphProfile;
+CallGraphSort::CallGraphSort(const COFFLinkerContext &ctx) : ctx(ctx) {
+ const MapVector<SectionPair, uint64_t> &profile = ctx.config.callGraphProfile;
DenseMap<const SectionChunk *, int> secToCluster;
auto getOrCreateNode = [&](const SectionChunk *isec) -> int {
};
// Create the graph.
- for (std::pair<SectionPair, uint64_t> &c : profile) {
+ for (const std::pair<SectionPair, uint64_t> &c : profile) {
const auto *fromSec = cast<SectionChunk>(c.first.first->repl);
const auto *toSec = cast<SectionChunk>(c.first.second->repl);
uint64_t weight = c.second;
// output. This messes with the cluster size and density calculations. We
// would also end up moving input sections in other output sections without
// moving them closer to what calls them.
- if (fromSec->getOutputSection() != toSec->getOutputSection())
+ if (ctx.getOutputSection(fromSec) != ctx.getOutputSection(toSec))
continue;
int from = getOrCreateNode(fromSec);
break;
}
}
- if (!config->printSymbolOrder.empty()) {
+ if (!ctx.config.printSymbolOrder.empty()) {
std::error_code ec;
- raw_fd_ostream os(config->printSymbolOrder, ec, sys::fs::OF_None);
+ raw_fd_ostream os(ctx.config.printSymbolOrder, ec, sys::fs::OF_None);
if (ec) {
- error("cannot open " + config->printSymbolOrder + ": " + ec.message());
+ error("cannot open " + ctx.config.printSymbolOrder + ": " + ec.message());
return orderMap;
}
// Print the symbols ordered by C3, in the order of increasing curOrder
// This first builds a call graph based on the profile data then merges sections
// according to the C³ heuristic. All clusters are then sorted by a density
// metric to further improve locality.
-DenseMap<const SectionChunk *, int> coff::computeCallGraphProfileOrder() {
- return CallGraphSort().run();
+DenseMap<const SectionChunk *, int>
+coff::computeCallGraphProfileOrder(const COFFLinkerContext &ctx) {
+ return CallGraphSort(ctx).run();
}
#include "llvm/ADT/DenseMap.h"
-namespace lld {
-namespace coff {
+namespace lld::coff {
class SectionChunk;
+class COFFLinkerContext;
-llvm::DenseMap<const SectionChunk *, int> computeCallGraphProfileOrder();
-} // namespace coff
-} // namespace lld
+llvm::DenseMap<const SectionChunk *, int>
+computeCallGraphProfileOrder(const COFFLinkerContext &ctx);
+} // namespace lld::coff
#endif
//===----------------------------------------------------------------------===//
#include "Chunks.h"
+#include "COFFLinkerContext.h"
#include "InputFiles.h"
+#include "SymbolTable.h"
#include "Symbols.h"
#include "Writer.h"
-#include "SymbolTable.h"
-#include "lld/Common/ErrorHandler.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/COFF.h"
#include "llvm/Object/COFF.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
+#include <iterator>
using namespace llvm;
using namespace llvm::object;
using namespace llvm::COFF;
using llvm::support::ulittle32_t;
-namespace lld {
-namespace coff {
+namespace lld::coff {
SectionChunk::SectionChunk(ObjFile *f, const coff_section *h)
: Chunk(SectionKind), file(f), header(h), repl(this) {
// enabled, treat non-comdat sections as roots. Generally optimized object
// files will be built with -ffunction-sections or /Gy, so most things worth
// stripping will be in a comdat.
- if (config)
- live = !config->doGC || !isCOMDAT();
+ if (file)
+ live = !file->ctx.config.doGC || !isCOMDAT();
else
live = true;
}
add32(off, secRel);
}
-static void applySecIdx(uint8_t *off, OutputSection *os) {
+static void applySecIdx(uint8_t *off, OutputSection *os,
+ unsigned numOutputSections) {
+ // numOutputSections is the largest valid section index. Make sure that
+ // it fits in 16 bits.
+ assert(numOutputSections <= 0xffff && "size of outputSections is too big");
+
// Absolute symbol doesn't have section index, but section index relocation
// against absolute symbol should be resolved to one plus the last output
// section index. This is required for compatibility with MSVC.
if (os)
add16(off, os->sectionIndex);
else
- add16(off, DefinedAbsolute::numOutputSections + 1);
+ add16(off, numOutputSections + 1);
}
void SectionChunk::applyRelX64(uint8_t *off, uint16_t type, OutputSection *os,
- uint64_t s, uint64_t p) const {
+ uint64_t s, uint64_t p,
+ uint64_t imageBase) const {
switch (type) {
- case IMAGE_REL_AMD64_ADDR32: add32(off, s + config->imageBase); break;
- case IMAGE_REL_AMD64_ADDR64: add64(off, s + config->imageBase); break;
+ case IMAGE_REL_AMD64_ADDR32:
+ add32(off, s + imageBase);
+ break;
+ case IMAGE_REL_AMD64_ADDR64:
+ add64(off, s + imageBase);
+ break;
case IMAGE_REL_AMD64_ADDR32NB: add32(off, s); break;
case IMAGE_REL_AMD64_REL32: add32(off, s - p - 4); break;
case IMAGE_REL_AMD64_REL32_1: add32(off, s - p - 5); break;
case IMAGE_REL_AMD64_REL32_3: add32(off, s - p - 7); break;
case IMAGE_REL_AMD64_REL32_4: add32(off, s - p - 8); break;
case IMAGE_REL_AMD64_REL32_5: add32(off, s - p - 9); break;
- case IMAGE_REL_AMD64_SECTION: applySecIdx(off, os); break;
+ case IMAGE_REL_AMD64_SECTION:
+ applySecIdx(off, os, file->ctx.outputSections.size());
+ break;
case IMAGE_REL_AMD64_SECREL: applySecRel(this, off, os, s); break;
default:
error("unsupported relocation type 0x" + Twine::utohexstr(type) + " in " +
}
void SectionChunk::applyRelX86(uint8_t *off, uint16_t type, OutputSection *os,
- uint64_t s, uint64_t p) const {
+ uint64_t s, uint64_t p,
+ uint64_t imageBase) const {
switch (type) {
case IMAGE_REL_I386_ABSOLUTE: break;
- case IMAGE_REL_I386_DIR32: add32(off, s + config->imageBase); break;
+ case IMAGE_REL_I386_DIR32:
+ add32(off, s + imageBase);
+ break;
case IMAGE_REL_I386_DIR32NB: add32(off, s); break;
case IMAGE_REL_I386_REL32: add32(off, s - p - 4); break;
- case IMAGE_REL_I386_SECTION: applySecIdx(off, os); break;
+ case IMAGE_REL_I386_SECTION:
+ applySecIdx(off, os, file->ctx.outputSections.size());
+ break;
case IMAGE_REL_I386_SECREL: applySecRel(this, off, os, s); break;
default:
error("unsupported relocation type 0x" + Twine::utohexstr(type) + " in " +
}
void SectionChunk::applyRelARM(uint8_t *off, uint16_t type, OutputSection *os,
- uint64_t s, uint64_t p) const {
+ uint64_t s, uint64_t p,
+ uint64_t imageBase) const {
// Pointer to thumb code must have the LSB set.
uint64_t sx = s;
if (os && (os->header.Characteristics & IMAGE_SCN_MEM_EXECUTE))
sx |= 1;
switch (type) {
- case IMAGE_REL_ARM_ADDR32: add32(off, sx + config->imageBase); break;
+ case IMAGE_REL_ARM_ADDR32:
+ add32(off, sx + imageBase);
+ break;
case IMAGE_REL_ARM_ADDR32NB: add32(off, sx); break;
- case IMAGE_REL_ARM_MOV32T: applyMOV32T(off, sx + config->imageBase); break;
+ case IMAGE_REL_ARM_MOV32T:
+ applyMOV32T(off, sx + imageBase);
+ break;
case IMAGE_REL_ARM_BRANCH20T: applyBranch20T(off, sx - p - 4); break;
case IMAGE_REL_ARM_BRANCH24T: applyBranch24T(off, sx - p - 4); break;
case IMAGE_REL_ARM_BLX23T: applyBranch24T(off, sx - p - 4); break;
- case IMAGE_REL_ARM_SECTION: applySecIdx(off, os); break;
+ case IMAGE_REL_ARM_SECTION:
+ applySecIdx(off, os, file->ctx.outputSections.size());
+ break;
case IMAGE_REL_ARM_SECREL: applySecRel(this, off, os, s); break;
case IMAGE_REL_ARM_REL32: add32(off, sx - p - 4); break;
default:
// the page offset from the current instruction to the target.
void applyArm64Addr(uint8_t *off, uint64_t s, uint64_t p, int shift) {
uint32_t orig = read32le(off);
- uint64_t imm = ((orig >> 29) & 0x3) | ((orig >> 3) & 0x1FFFFC);
+ int64_t imm =
+ SignExtend64<21>(((orig >> 29) & 0x3) | ((orig >> 3) & 0x1FFFFC));
s += imm;
imm = (s >> shift) - (p >> shift);
uint32_t immLo = (imm & 0x3) << 29;
}
void SectionChunk::applyRelARM64(uint8_t *off, uint16_t type, OutputSection *os,
- uint64_t s, uint64_t p) const {
+ uint64_t s, uint64_t p,
+ uint64_t imageBase) const {
switch (type) {
case IMAGE_REL_ARM64_PAGEBASE_REL21: applyArm64Addr(off, s, p, 12); break;
case IMAGE_REL_ARM64_REL21: applyArm64Addr(off, s, p, 0); break;
case IMAGE_REL_ARM64_BRANCH26: applyArm64Branch26(off, s - p); break;
case IMAGE_REL_ARM64_BRANCH19: applyArm64Branch19(off, s - p); break;
case IMAGE_REL_ARM64_BRANCH14: applyArm64Branch14(off, s - p); break;
- case IMAGE_REL_ARM64_ADDR32: add32(off, s + config->imageBase); break;
+ case IMAGE_REL_ARM64_ADDR32:
+ add32(off, s + imageBase);
+ break;
case IMAGE_REL_ARM64_ADDR32NB: add32(off, s); break;
- case IMAGE_REL_ARM64_ADDR64: add64(off, s + config->imageBase); break;
+ case IMAGE_REL_ARM64_ADDR64:
+ add64(off, s + imageBase);
+ break;
case IMAGE_REL_ARM64_SECREL: applySecRel(this, off, os, s); break;
case IMAGE_REL_ARM64_SECREL_LOW12A: applySecRelLow12A(this, off, os, s); break;
case IMAGE_REL_ARM64_SECREL_HIGH12A: applySecRelHigh12A(this, off, os, s); break;
case IMAGE_REL_ARM64_SECREL_LOW12L: applySecRelLdr(this, off, os, s); break;
- case IMAGE_REL_ARM64_SECTION: applySecIdx(off, os); break;
+ case IMAGE_REL_ARM64_SECTION:
+ applySecIdx(off, os, file->ctx.outputSections.size());
+ break;
case IMAGE_REL_ARM64_REL32: add32(off, s - p - 4); break;
default:
error("unsupported relocation type 0x" + Twine::utohexstr(type) + " in " +
static void maybeReportRelocationToDiscarded(const SectionChunk *fromChunk,
Defined *sym,
- const coff_relocation &rel) {
+ const coff_relocation &rel,
+ bool isMinGW) {
// Don't report these errors when the relocation comes from a debug info
// section or in mingw mode. MinGW mode object files (built by GCC) can
// have leftover sections with relocations against discarded comdat
// sections. Such sections are left as is, with relocations untouched.
- if (fromChunk->isCodeView() || fromChunk->isDWARF() || config->mingw)
+ if (fromChunk->isCodeView() || fromChunk->isDWARF() || isMinGW)
return;
// Get the name of the symbol. If it's null, it was discarded early, so we
// section is needed to compute SECREL and SECTION relocations used in debug
// info.
Chunk *c = sym ? sym->getChunk() : nullptr;
- OutputSection *os = c ? c->getOutputSection() : nullptr;
+ OutputSection *os = c ? file->ctx.getOutputSection(c) : nullptr;
// Skip the relocation if it refers to a discarded section, and diagnose it
// as an error if appropriate. If a symbol was discarded early, it may be
// it was an absolute or synthetic symbol.
if (!sym ||
(!os && !isa<DefinedAbsolute>(sym) && !isa<DefinedSynthetic>(sym))) {
- maybeReportRelocationToDiscarded(this, sym, rel);
+ maybeReportRelocationToDiscarded(this, sym, rel, file->ctx.config.mingw);
return;
}
// Compute the RVA of the relocation for relative relocations.
uint64_t p = rva + rel.VirtualAddress;
- switch (config->machine) {
+ uint64_t imageBase = file->ctx.config.imageBase;
+ switch (file->ctx.config.machine) {
case AMD64:
- applyRelX64(off, rel.Type, os, s, p);
+ applyRelX64(off, rel.Type, os, s, p, imageBase);
break;
case I386:
- applyRelX86(off, rel.Type, os, s, p);
+ applyRelX86(off, rel.Type, os, s, p, imageBase);
break;
case ARMNT:
- applyRelARM(off, rel.Type, os, s, p);
+ applyRelARM(off, rel.Type, os, s, p, imageBase);
break;
case ARM64:
- applyRelARM64(off, rel.Type, os, s, p);
+ applyRelARM64(off, rel.Type, os, s, p, imageBase);
break;
default:
llvm_unreachable("unknown machine type");
return;
warn("some relocations in " + file->getName() + " are not sorted");
MutableArrayRef<coff_relocation> newRelocs(
- bAlloc.Allocate<coff_relocation>(relocsSize), relocsSize);
+ bAlloc().Allocate<coff_relocation>(relocsSize), relocsSize);
memcpy(newRelocs.data(), relocsData, relocsSize * sizeof(coff_relocation));
llvm::sort(newRelocs, cmpByVa);
setRelocs(newRelocs);
child->assocChildren = next;
}
-static uint8_t getBaserelType(const coff_relocation &rel) {
- switch (config->machine) {
+static uint8_t getBaserelType(const coff_relocation &rel,
+ llvm::COFF::MachineTypes machine) {
+ switch (machine) {
case AMD64:
if (rel.Type == IMAGE_REL_AMD64_ADDR64)
return IMAGE_REL_BASED_DIR64;
// Only called when base relocation is enabled.
void SectionChunk::getBaserels(std::vector<Baserel> *res) {
for (const coff_relocation &rel : getRelocs()) {
- uint8_t ty = getBaserelType(rel);
+ uint8_t ty = getBaserelType(rel, file->ctx.config.machine);
if (ty == IMAGE_REL_BASED_ABSOLUTE)
continue;
Symbol *target = file->getSymbol(rel.SymbolTableIndex);
// another DLL) This returns the size the relocation is supposed to update,
// in bits, or 0 if the relocation cannot be handled as a runtime pseudo
// relocation.
-static int getRuntimePseudoRelocSize(uint16_t type) {
+static int getRuntimePseudoRelocSize(uint16_t type,
+ llvm::COFF::MachineTypes machine) {
// Relocations that either contain an absolute address, or a plain
// relative offset, since the runtime pseudo reloc implementation
// adds 8/16/32/64 bit values to a memory address.
// the image, or temporarily changed at runtime with VirtualProtect.
// Since this only operates on direct address values, it doesn't work for
// ARM/ARM64 relocations, other than the plain ADDR32/ADDR64 relocations.
- switch (config->machine) {
+ switch (machine) {
case AMD64:
switch (type) {
case IMAGE_REL_AMD64_ADDR64:
dyn_cast_or_null<Defined>(file->getSymbol(rel.SymbolTableIndex));
if (!target || !target->isRuntimePseudoReloc)
continue;
- int sizeInBits = getRuntimePseudoRelocSize(rel.Type);
+ int sizeInBits =
+ getRuntimePseudoRelocSize(rel.Type, file->ctx.config.machine);
if (sizeInBits == 0) {
error("unable to automatically import from " + target->getName() +
" with relocation type " +
// Removed by dead-stripping. If it's removed by ICF, ICF already
// printed out the name, so don't repeat that here.
if (sym && this == repl)
- message("Discarded " + sym->getName());
+ log("Discarded " + sym->getName());
}
StringRef SectionChunk::getDebugName() const {
buf[str.size()] = '\0';
}
-ImportThunkChunkX64::ImportThunkChunkX64(Defined *s) : ImportThunkChunk(s) {
+ImportThunkChunkX64::ImportThunkChunkX64(COFFLinkerContext &ctx, Defined *s)
+ : ImportThunkChunk(ctx, s) {
// Intel Optimization Manual says that all branch targets
// should be 16-byte aligned. MSVC linker does this too.
setAlignment(16);
}
void ImportThunkChunkX86::getBaserels(std::vector<Baserel> *res) {
- res->emplace_back(getRVA() + 2);
+ res->emplace_back(getRVA() + 2, ctx.config.machine);
}
void ImportThunkChunkX86::writeTo(uint8_t *buf) const {
memcpy(buf, importThunkX86, sizeof(importThunkX86));
// The first two bytes is a JMP instruction. Fill its operand.
- write32le(buf + 2,
- impSymbol->getRVA() + config->imageBase);
+ write32le(buf + 2, impSymbol->getRVA() + ctx.config.imageBase);
}
void ImportThunkChunkARM::getBaserels(std::vector<Baserel> *res) {
void ImportThunkChunkARM::writeTo(uint8_t *buf) const {
memcpy(buf, importThunkARM, sizeof(importThunkARM));
// Fix mov.w and mov.t operands.
- applyMOV32T(buf, impSymbol->getRVA() + config->imageBase);
+ applyMOV32T(buf, impSymbol->getRVA() + ctx.config.imageBase);
}
void ImportThunkChunkARM64::writeTo(uint8_t *buf) const {
};
size_t RangeExtensionThunkARM::getSize() const {
- assert(config->machine == ARMNT);
+ assert(ctx.config.machine == ARMNT);
+ (void)&ctx;
return sizeof(armThunk);
}
void RangeExtensionThunkARM::writeTo(uint8_t *buf) const {
- assert(config->machine == ARMNT);
+ assert(ctx.config.machine == ARMNT);
uint64_t offset = target->getRVA() - rva - 12;
memcpy(buf, armThunk, sizeof(armThunk));
applyMOV32T(buf, uint32_t(offset));
};
size_t RangeExtensionThunkARM64::getSize() const {
- assert(config->machine == ARM64);
+ assert(ctx.config.machine == ARM64);
+ (void)&ctx;
return sizeof(arm64Thunk);
}
void RangeExtensionThunkARM64::writeTo(uint8_t *buf) const {
- assert(config->machine == ARM64);
+ assert(ctx.config.machine == ARM64);
memcpy(buf, arm64Thunk, sizeof(arm64Thunk));
applyArm64Addr(buf + 0, target->getRVA(), rva, 12);
applyArm64Imm(buf + 4, target->getRVA() & 0xfff, 0);
}
+LocalImportChunk::LocalImportChunk(COFFLinkerContext &c, Defined *s)
+ : sym(s), ctx(c) {
+ setAlignment(ctx.config.wordsize);
+}
+
void LocalImportChunk::getBaserels(std::vector<Baserel> *res) {
- res->emplace_back(getRVA());
+ res->emplace_back(getRVA(), ctx.config.machine);
}
-size_t LocalImportChunk::getSize() const { return config->wordsize; }
+size_t LocalImportChunk::getSize() const { return ctx.config.wordsize; }
void LocalImportChunk::writeTo(uint8_t *buf) const {
- if (config->is64()) {
- write64le(buf, sym->getRVA() + config->imageBase);
+ if (ctx.config.is64()) {
+ write64le(buf, sym->getRVA() + ctx.config.imageBase);
} else {
- write32le(buf, sym->getRVA() + config->imageBase);
+ write32le(buf, sym->getRVA() + ctx.config.imageBase);
}
}
size_t cnt = 0;
for (const ChunkAndOffset &co : syms)
begin[cnt++] = co.inputChunk->getRVA() + co.offset;
- std::sort(begin, begin + cnt);
+ llvm::sort(begin, begin + cnt);
assert(std::unique(begin, begin + cnt) == begin + cnt &&
"RVA tables should be de-duplicated");
}
uint8_t flag;
};
auto flags =
- makeMutableArrayRef(reinterpret_cast<RVAFlag *>(buf), syms.size());
+ MutableArrayRef(reinterpret_cast<RVAFlag *>(buf), syms.size());
for (auto t : zip(syms, flags)) {
const auto &sym = std::get<0>(t);
auto &flag = std::get<1>(t);
memcpy(buf, data.data(), data.size());
}
-uint8_t Baserel::getDefaultType() {
- switch (config->machine) {
+uint8_t Baserel::getDefaultType(llvm::COFF::MachineTypes machine) {
+ switch (machine) {
case AMD64:
case ARM64:
return IMAGE_REL_BASED_DIR64;
}
}
-MergeChunk *MergeChunk::instances[Log2MaxSectionAlignment + 1] = {};
-
MergeChunk::MergeChunk(uint32_t alignment)
- : builder(StringTableBuilder::RAW, alignment) {
+ : builder(StringTableBuilder::RAW, llvm::Align(alignment)) {
setAlignment(alignment);
}
-void MergeChunk::addSection(SectionChunk *c) {
+void MergeChunk::addSection(COFFLinkerContext &ctx, SectionChunk *c) {
assert(isPowerOf2_32(c->getAlignment()));
uint8_t p2Align = llvm::Log2_32(c->getAlignment());
- assert(p2Align < array_lengthof(instances));
- auto *&mc = instances[p2Align];
+ assert(p2Align < std::size(ctx.mergeChunkInstances));
+ auto *&mc = ctx.mergeChunkInstances[p2Align];
if (!mc)
mc = make<MergeChunk>(c->getAlignment());
mc->sections.push_back(c);
}
// MinGW specific.
-size_t AbsolutePointerChunk::getSize() const { return config->wordsize; }
+size_t AbsolutePointerChunk::getSize() const { return ctx.config.wordsize; }
void AbsolutePointerChunk::writeTo(uint8_t *buf) const {
- if (config->is64()) {
+ if (ctx.config.is64()) {
write64le(buf, value);
} else {
write32le(buf, value);
}
}
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#include <utility>
#include <vector>
-namespace lld {
-namespace coff {
+namespace lld::coff {
using llvm::COFF::ImportDirectoryTableEntry;
using llvm::object::COFFSymbolRef;
// chunk has a back pointer to an output section.
void setOutputSectionIdx(uint16_t o) { osidx = o; }
uint16_t getOutputSectionIdx() const { return osidx; }
- OutputSection *getOutputSection() const;
// Windows-specific.
// Collect all locations that contain absolute addresses for base relocations.
NonSectionChunk(Kind k = OtherKind) : Chunk(k) {}
};
+// MinGW specific; information about one individual location in the image
+// that needs to be fixed up at runtime after loading. This represents
+// one individual element in the PseudoRelocTableChunk table.
+class RuntimePseudoReloc {
+public:
+ RuntimePseudoReloc(Defined *sym, SectionChunk *target, uint32_t targetOffset,
+ int flags)
+ : sym(sym), target(target), targetOffset(targetOffset), flags(flags) {}
+
+ Defined *sym;
+ SectionChunk *target;
+ uint32_t targetOffset;
+ // The Flags field contains the size of the relocation, in bits. No other
+ // flags are currently defined.
+ int flags;
+};
+
// A chunk corresponding a section of an input file.
class SectionChunk final : public Chunk {
// Identical COMDAT Folding feature accesses section internal data.
bool isCOMDAT() const;
void applyRelocation(uint8_t *off, const coff_relocation &rel) const;
void applyRelX64(uint8_t *off, uint16_t type, OutputSection *os, uint64_t s,
- uint64_t p) const;
+ uint64_t p, uint64_t imageBase) const;
void applyRelX86(uint8_t *off, uint16_t type, OutputSection *os, uint64_t s,
- uint64_t p) const;
+ uint64_t p, uint64_t imageBase) const;
void applyRelARM(uint8_t *off, uint16_t type, OutputSection *os, uint64_t s,
- uint64_t p) const;
+ uint64_t p, uint64_t imageBase) const;
void applyRelARM64(uint8_t *off, uint16_t type, OutputSection *os, uint64_t s,
- uint64_t p) const;
+ uint64_t p, uint64_t imageBase) const;
void getRuntimePseudoRelocs(std::vector<RuntimePseudoReloc> &res);
}
ArrayRef<coff_relocation> getRelocs() const {
- return llvm::makeArrayRef(relocsData, relocsSize);
+ return llvm::ArrayRef(relocsData, relocsSize);
}
// Reloc setter used by ARM range extension thunk insertion.
class MergeChunk : public NonSectionChunk {
public:
MergeChunk(uint32_t alignment);
- static void addSection(SectionChunk *c);
+ static void addSection(COFFLinkerContext &ctx, SectionChunk *c);
void finalizeContents();
void assignSubsectionRVAs();
size_t getSize() const override;
void writeTo(uint8_t *buf) const override;
- static MergeChunk *instances[Log2MaxSectionAlignment + 1];
std::vector<SectionChunk *> sections;
private:
// contents will be a JMP instruction to some __imp_ symbol.
class ImportThunkChunk : public NonSectionChunk {
public:
- ImportThunkChunk(Defined *s)
- : NonSectionChunk(ImportThunkKind), impSymbol(s) {}
+ ImportThunkChunk(COFFLinkerContext &ctx, Defined *s)
+ : NonSectionChunk(ImportThunkKind), impSymbol(s), ctx(ctx) {}
static bool classof(const Chunk *c) { return c->kind() == ImportThunkKind; }
protected:
Defined *impSymbol;
+ COFFLinkerContext &ctx;
};
class ImportThunkChunkX64 : public ImportThunkChunk {
public:
- explicit ImportThunkChunkX64(Defined *s);
+ explicit ImportThunkChunkX64(COFFLinkerContext &ctx, Defined *s);
size_t getSize() const override { return sizeof(importThunkX86); }
void writeTo(uint8_t *buf) const override;
};
class ImportThunkChunkX86 : public ImportThunkChunk {
public:
- explicit ImportThunkChunkX86(Defined *s) : ImportThunkChunk(s) {}
+ explicit ImportThunkChunkX86(COFFLinkerContext &ctx, Defined *s)
+ : ImportThunkChunk(ctx, s) {}
size_t getSize() const override { return sizeof(importThunkX86); }
void getBaserels(std::vector<Baserel> *res) override;
void writeTo(uint8_t *buf) const override;
class ImportThunkChunkARM : public ImportThunkChunk {
public:
- explicit ImportThunkChunkARM(Defined *s) : ImportThunkChunk(s) {
+ explicit ImportThunkChunkARM(COFFLinkerContext &ctx, Defined *s)
+ : ImportThunkChunk(ctx, s) {
setAlignment(2);
}
size_t getSize() const override { return sizeof(importThunkARM); }
class ImportThunkChunkARM64 : public ImportThunkChunk {
public:
- explicit ImportThunkChunkARM64(Defined *s) : ImportThunkChunk(s) {
+ explicit ImportThunkChunkARM64(COFFLinkerContext &ctx, Defined *s)
+ : ImportThunkChunk(ctx, s) {
setAlignment(4);
}
size_t getSize() const override { return sizeof(importThunkARM64); }
class RangeExtensionThunkARM : public NonSectionChunk {
public:
- explicit RangeExtensionThunkARM(Defined *t) : target(t) { setAlignment(2); }
+ explicit RangeExtensionThunkARM(COFFLinkerContext &ctx, Defined *t)
+ : target(t), ctx(ctx) {
+ setAlignment(2);
+ }
size_t getSize() const override;
void writeTo(uint8_t *buf) const override;
Defined *target;
+
+private:
+ COFFLinkerContext &ctx;
};
class RangeExtensionThunkARM64 : public NonSectionChunk {
public:
- explicit RangeExtensionThunkARM64(Defined *t) : target(t) { setAlignment(4); }
+ explicit RangeExtensionThunkARM64(COFFLinkerContext &ctx, Defined *t)
+ : target(t), ctx(ctx) {
+ setAlignment(4);
+ }
size_t getSize() const override;
void writeTo(uint8_t *buf) const override;
Defined *target;
+
+private:
+ COFFLinkerContext &ctx;
};
// Windows-specific.
// See comments for DefinedLocalImport class.
class LocalImportChunk : public NonSectionChunk {
public:
- explicit LocalImportChunk(Defined *s) : sym(s) {
- setAlignment(config->wordsize);
- }
+ explicit LocalImportChunk(COFFLinkerContext &ctx, Defined *s);
size_t getSize() const override;
void getBaserels(std::vector<Baserel> *res) override;
void writeTo(uint8_t *buf) const override;
private:
Defined *sym;
+ COFFLinkerContext &ctx;
};
// Duplicate RVAs are not allowed in RVA tables, so unique symbols by chunk and
class Baserel {
public:
Baserel(uint32_t v, uint8_t ty) : rva(v), type(ty) {}
- explicit Baserel(uint32_t v) : Baserel(v, getDefaultType()) {}
- uint8_t getDefaultType();
+ explicit Baserel(uint32_t v, llvm::COFF::MachineTypes machine)
+ : Baserel(v, getDefaultType(machine)) {}
+ uint8_t getDefaultType(llvm::COFF::MachineTypes machine);
uint32_t rva;
uint8_t type;
std::vector<RuntimePseudoReloc> relocs;
};
-// MinGW specific; information about one individual location in the image
-// that needs to be fixed up at runtime after loading. This represents
-// one individual element in the PseudoRelocTableChunk table.
-class RuntimePseudoReloc {
-public:
- RuntimePseudoReloc(Defined *sym, SectionChunk *target, uint32_t targetOffset,
- int flags)
- : sym(sym), target(target), targetOffset(targetOffset), flags(flags) {}
-
- Defined *sym;
- SectionChunk *target;
- uint32_t targetOffset;
- // The Flags field contains the size of the relocation, in bits. No other
- // flags are currently defined.
- int flags;
-};
-
// MinGW specific. A Chunk that contains one pointer-sized absolute value.
class AbsolutePointerChunk : public NonSectionChunk {
public:
- AbsolutePointerChunk(uint64_t value) : value(value) {
+ AbsolutePointerChunk(COFFLinkerContext &ctx, uint64_t value)
+ : value(value), ctx(ctx) {
setAlignment(getSize());
}
size_t getSize() const override;
private:
uint64_t value;
+ COFFLinkerContext &ctx;
};
// Return true if this file has the hotpatch flag set to true in the S_COMPILE3
void applyArm64Imm(uint8_t *off, uint64_t imm, uint32_t rangeLimit);
void applyArm64Branch26(uint8_t *off, int64_t v);
-} // namespace coff
-} // namespace lld
+// Convenience class for initializing a coff_section with specific flags.
+class FakeSection {
+public:
+ FakeSection(int c) { section.Characteristics = c; }
+
+ coff_section section;
+};
+
+// Convenience class for initializing a SectionChunk with specific flags.
+class FakeSectionChunk {
+public:
+ FakeSectionChunk(const coff_section *section) : chunk(nullptr, section) {
+ // Comdats from LTO files can't be fully treated as regular comdats
+ // at this point; we don't know what size or contents they are going to
+ // have, so we can't do proper checking of such aspects of them.
+ chunk.selection = llvm::COFF::IMAGE_COMDAT_SELECT_ANY;
+ }
+
+ SectionChunk chunk;
+};
+
+} // namespace lld::coff
namespace llvm {
template <>
#define LLD_COFF_CONFIG_H
#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Object/COFF.h"
#include "llvm/Support/CachePruning.h"
+#include "llvm/Support/VirtualFileSystem.h"
#include <cstdint>
#include <map>
#include <set>
#include <string>
-namespace lld {
-namespace coff {
+namespace lld::coff {
using llvm::COFF::IMAGE_FILE_MACHINE_UNKNOWN;
using llvm::COFF::WindowsSubsystem;
struct Export {
StringRef name; // N in /export:N or /export:E=N
StringRef extName; // E in /export:E=N
+ StringRef aliasTarget; // GNU specific: N in "alias == N"
Symbol *sym = nullptr;
uint16_t ordinal = 0;
bool noname = false;
bool operator==(const Export &e) {
return (name == e.name && extName == e.extName &&
+ aliasTarget == e.aliasTarget &&
ordinal == e.ordinal && noname == e.noname &&
data == e.data && isPrivate == e.isPrivate);
}
// Global configuration.
struct Configuration {
- enum ManifestKind { SideBySide, Embed, No };
- bool is64() { return machine == AMD64 || machine == ARM64; }
+ enum ManifestKind { Default, SideBySide, Embed, No };
+ bool is64() const { return machine == AMD64 || machine == ARM64; }
llvm::COFF::MachineTypes machine = IMAGE_FILE_MACHINE_UNKNOWN;
size_t wordsize;
bool showTiming = false;
bool showSummary = false;
unsigned debugTypes = static_cast<unsigned>(DebugType::None);
+ llvm::SmallVector<llvm::StringRef, 0> mllvmOpts;
std::vector<std::string> natvisFiles;
llvm::StringMap<std::string> namedStreams;
llvm::SmallString<128> pdbAltPath;
+ int pdbPageSize = 4096;
llvm::SmallString<128> pdbPath;
llvm::SmallString<128> pdbSourcePath;
std::vector<llvm::StringRef> argv;
// True if we are creating a DLL.
bool dll = false;
StringRef implib;
+ bool noimplib = false;
std::vector<Export> exports;
bool hadExplicitExports;
std::set<std::string> delayLoads;
// Used for /opt:lldltocachepolicy=policy
llvm::CachePruningPolicy ltoCachePolicy;
- // Used for /opt:[no]ltonewpassmanager
- bool ltoNewPassManager = false;
// Used for /opt:[no]ltodebugpassmanager
bool ltoDebugPassManager = false;
std::map<StringRef, uint32_t> section;
// Options for manifest files.
- ManifestKind manifest = No;
+ ManifestKind manifest = Default;
int manifestID = 1;
- StringRef manifestDependency;
+ llvm::SetVector<StringRef> manifestDependencies;
bool manifestUAC = true;
std::vector<std::string> manifestInput;
StringRef manifestLevel = "'asInvoker'";
// Used for /map.
std::string mapFile;
+ // Used for /mapinfo.
+ bool mapInfo = false;
+
// Used for /thinlto-index-only:
llvm::StringRef thinLTOIndexOnlyArg;
// Used for /lto-cs-profile-path
llvm::StringRef ltoCSProfileFile;
+ // Used for /lto-pgo-warn-mismatch:
+ bool ltoPGOWarnMismatch = true;
+
// Used for /call-graph-ordering-file:
llvm::MapVector<std::pair<const SectionChunk *, const SectionChunk *>,
uint64_t>
// Used for /print-symbol-order:
StringRef printSymbolOrder;
+ // Used for /vfsoverlay:
+ std::unique_ptr<llvm::vfs::FileSystem> vfs;
+
uint64_t align = 4096;
uint64_t imageBase = -1;
uint64_t fileAlign = 512;
bool autoImport = false;
bool pseudoRelocs = false;
bool stdcallFixup = false;
+ bool writeCheckSum = false;
};
-extern Configuration *config;
-
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
//===----------------------------------------------------------------------===//
#include "DLL.h"
+#include "COFFLinkerContext.h"
#include "Chunks.h"
#include "SymbolTable.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/Object/COFF.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Path.h"
using namespace llvm::support::endian;
using namespace llvm::COFF;
-namespace lld {
-namespace coff {
+namespace lld::coff {
namespace {
// Import table
// A chunk for the import descriptor table.
class LookupChunk : public NonSectionChunk {
public:
- explicit LookupChunk(Chunk *c) : hintName(c) {
- setAlignment(config->wordsize);
+ explicit LookupChunk(COFFLinkerContext &ctx, Chunk *c)
+ : hintName(c), ctx(ctx) {
+ setAlignment(ctx.config.wordsize);
}
- size_t getSize() const override { return config->wordsize; }
+ size_t getSize() const override { return ctx.config.wordsize; }
void writeTo(uint8_t *buf) const override {
- if (config->is64())
+ if (ctx.config.is64())
write64le(buf, hintName->getRVA());
else
write32le(buf, hintName->getRVA());
}
Chunk *hintName;
+
+private:
+ COFFLinkerContext &ctx;
};
// A chunk for the import descriptor table.
// See Microsoft PE/COFF spec 7.1. Import Header for details.
class OrdinalOnlyChunk : public NonSectionChunk {
public:
- explicit OrdinalOnlyChunk(uint16_t v) : ordinal(v) {
- setAlignment(config->wordsize);
+ explicit OrdinalOnlyChunk(COFFLinkerContext &c, uint16_t v)
+ : ordinal(v), ctx(c) {
+ setAlignment(ctx.config.wordsize);
}
- size_t getSize() const override { return config->wordsize; }
+ size_t getSize() const override { return ctx.config.wordsize; }
void writeTo(uint8_t *buf) const override {
// An import-by-ordinal slot has MSB 1 to indicate that
// this is import-by-ordinal (and not import-by-name).
- if (config->is64()) {
+ if (ctx.config.is64()) {
write64le(buf, (1ULL << 63) | ordinal);
} else {
write32le(buf, (1ULL << 31) | ordinal);
}
uint16_t ordinal;
+
+private:
+ COFFLinkerContext &ctx;
};
// A chunk for the import descriptor table.
};
static std::vector<std::vector<DefinedImportData *>>
-binImports(const std::vector<DefinedImportData *> &imports) {
+binImports(COFFLinkerContext &ctx,
+ const std::vector<DefinedImportData *> &imports) {
// Group DLL-imported symbols by DLL name because that's how
// symbols are laid out in the import descriptor table.
- auto less = [](const std::string &a, const std::string &b) {
- return config->dllOrder[a] < config->dllOrder[b];
+ auto less = [&ctx](const std::string &a, const std::string &b) {
+ return ctx.config.dllOrder[a] < ctx.config.dllOrder[b];
};
- std::map<std::string, std::vector<DefinedImportData *>,
- bool(*)(const std::string &, const std::string &)> m(less);
+ std::map<std::string, std::vector<DefinedImportData *>, decltype(less)> m(
+ less);
for (DefinedImportData *sym : imports)
m[sym->getDLLName().lower()].push_back(sym);
for (auto &kv : m) {
// Sort symbols by name for each group.
std::vector<DefinedImportData *> &syms = kv.second;
- std::sort(syms.begin(), syms.end(),
- [](DefinedImportData *a, DefinedImportData *b) {
- return a->getName() < b->getName();
- });
+ llvm::sort(syms, [](DefinedImportData *a, DefinedImportData *b) {
+ return a->getName() < b->getName();
+ });
v.push_back(std::move(syms));
}
return v;
0xFF, 0xE0, // jmp rax
};
+static const uint8_t tailMergeUnwindInfoX64[] = {
+ 0x01, // Version=1, Flags=UNW_FLAG_NHANDLER
+ 0x0a, // Size of prolog
+ 0x05, // Count of unwind codes
+ 0x00, // No frame register
+ 0x0a, 0x82, // Offset 0xa: UWOP_ALLOC_SMALL(0x48)
+ 0x06, 0x02, // Offset 6: UWOP_ALLOC_SMALL(8)
+ 0x04, 0x02, // Offset 4: UWOP_ALLOC_SMALL(8)
+ 0x02, 0x02, // Offset 2: UWOP_ALLOC_SMALL(8)
+ 0x01, 0x02, // Offset 1: UWOP_ALLOC_SMALL(8)
+ 0x00, 0x00 // Padding to align on 32-bits
+};
+
static const uint8_t thunkX86[] = {
0xB8, 0, 0, 0, 0, // mov eax, offset ___imp__<FUNCNAME>
0xE9, 0, 0, 0, 0, // jmp __tailMerge_<lib>
Defined *helper = nullptr;
};
+class TailMergePDataChunkX64 : public NonSectionChunk {
+public:
+ TailMergePDataChunkX64(Chunk *tm, Chunk *unwind) : tm(tm), unwind(unwind) {
+ // See
+ // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function
+ setAlignment(4);
+ }
+
+ size_t getSize() const override { return 3 * sizeof(uint32_t); }
+
+ void writeTo(uint8_t *buf) const override {
+ write32le(buf + 0, tm->getRVA()); // TailMergeChunk start RVA
+ write32le(buf + 4, tm->getRVA() + tm->getSize()); // TailMergeChunk stop RVA
+ write32le(buf + 8, unwind->getRVA()); // UnwindInfo RVA
+ }
+
+ Chunk *tm = nullptr;
+ Chunk *unwind = nullptr;
+};
+
+class TailMergeUnwindInfoX64 : public NonSectionChunk {
+public:
+ TailMergeUnwindInfoX64() {
+ // See
+ // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-unwind_info
+ setAlignment(4);
+ }
+
+ size_t getSize() const override { return sizeof(tailMergeUnwindInfoX64); }
+
+ void writeTo(uint8_t *buf) const override {
+ memcpy(buf, tailMergeUnwindInfoX64, sizeof(tailMergeUnwindInfoX64));
+ }
+};
+
class ThunkChunkX86 : public NonSectionChunk {
public:
- ThunkChunkX86(Defined *i, Chunk *tm) : imp(i), tailMerge(tm) {}
+ ThunkChunkX86(COFFLinkerContext &ctx, Defined *i, Chunk *tm)
+ : imp(i), tailMerge(tm), ctx(ctx) {}
size_t getSize() const override { return sizeof(thunkX86); }
void writeTo(uint8_t *buf) const override {
memcpy(buf, thunkX86, sizeof(thunkX86));
- write32le(buf + 1, imp->getRVA() + config->imageBase);
+ write32le(buf + 1, imp->getRVA() + ctx.config.imageBase);
write32le(buf + 6, tailMerge->getRVA() - rva - 10);
}
void getBaserels(std::vector<Baserel> *res) override {
- res->emplace_back(rva + 1);
+ res->emplace_back(rva + 1, ctx.config.machine);
}
Defined *imp = nullptr;
Chunk *tailMerge = nullptr;
+
+private:
+ const COFFLinkerContext &ctx;
};
class TailMergeChunkX86 : public NonSectionChunk {
public:
- TailMergeChunkX86(Chunk *d, Defined *h) : desc(d), helper(h) {}
+ TailMergeChunkX86(COFFLinkerContext &ctx, Chunk *d, Defined *h)
+ : desc(d), helper(h), ctx(ctx) {}
size_t getSize() const override { return sizeof(tailMergeX86); }
void writeTo(uint8_t *buf) const override {
memcpy(buf, tailMergeX86, sizeof(tailMergeX86));
- write32le(buf + 4, desc->getRVA() + config->imageBase);
+ write32le(buf + 4, desc->getRVA() + ctx.config.imageBase);
write32le(buf + 9, helper->getRVA() - rva - 13);
}
void getBaserels(std::vector<Baserel> *res) override {
- res->emplace_back(rva + 4);
+ res->emplace_back(rva + 4, ctx.config.machine);
}
Chunk *desc = nullptr;
Defined *helper = nullptr;
+
+private:
+ const COFFLinkerContext &ctx;
};
class ThunkChunkARM : public NonSectionChunk {
public:
- ThunkChunkARM(Defined *i, Chunk *tm) : imp(i), tailMerge(tm) {
+ ThunkChunkARM(COFFLinkerContext &ctx, Defined *i, Chunk *tm)
+ : imp(i), tailMerge(tm), ctx(ctx) {
setAlignment(2);
}
void writeTo(uint8_t *buf) const override {
memcpy(buf, thunkARM, sizeof(thunkARM));
- applyMOV32T(buf + 0, imp->getRVA() + config->imageBase);
+ applyMOV32T(buf + 0, imp->getRVA() + ctx.config.imageBase);
applyBranch24T(buf + 8, tailMerge->getRVA() - rva - 12);
}
Defined *imp = nullptr;
Chunk *tailMerge = nullptr;
+
+private:
+ const COFFLinkerContext &ctx;
};
class TailMergeChunkARM : public NonSectionChunk {
public:
- TailMergeChunkARM(Chunk *d, Defined *h) : desc(d), helper(h) {
+ TailMergeChunkARM(COFFLinkerContext &ctx, Chunk *d, Defined *h)
+ : desc(d), helper(h), ctx(ctx) {
setAlignment(2);
}
void writeTo(uint8_t *buf) const override {
memcpy(buf, tailMergeARM, sizeof(tailMergeARM));
- applyMOV32T(buf + 14, desc->getRVA() + config->imageBase);
+ applyMOV32T(buf + 14, desc->getRVA() + ctx.config.imageBase);
applyBranch24T(buf + 22, helper->getRVA() - rva - 26);
}
Chunk *desc = nullptr;
Defined *helper = nullptr;
+
+private:
+ const COFFLinkerContext &ctx;
};
class ThunkChunkARM64 : public NonSectionChunk {
// A chunk for the import descriptor table.
class DelayAddressChunk : public NonSectionChunk {
public:
- explicit DelayAddressChunk(Chunk *c) : thunk(c) {
- setAlignment(config->wordsize);
+ explicit DelayAddressChunk(COFFLinkerContext &ctx, Chunk *c)
+ : thunk(c), ctx(ctx) {
+ setAlignment(ctx.config.wordsize);
}
- size_t getSize() const override { return config->wordsize; }
+ size_t getSize() const override { return ctx.config.wordsize; }
void writeTo(uint8_t *buf) const override {
- if (config->is64()) {
- write64le(buf, thunk->getRVA() + config->imageBase);
+ if (ctx.config.is64()) {
+ write64le(buf, thunk->getRVA() + ctx.config.imageBase);
} else {
uint32_t bit = 0;
// Pointer to thumb code must have the LSB set, so adjust it.
- if (config->machine == ARMNT)
+ if (ctx.config.machine == ARMNT)
bit = 1;
- write32le(buf, (thunk->getRVA() + config->imageBase) | bit);
+ write32le(buf, (thunk->getRVA() + ctx.config.imageBase) | bit);
}
}
void getBaserels(std::vector<Baserel> *res) override {
- res->emplace_back(rva);
+ res->emplace_back(rva, ctx.config.machine);
}
Chunk *thunk;
+
+private:
+ const COFFLinkerContext &ctx;
};
// Export table
auto *e = (export_directory_table_entry *)(buf);
e->NameRVA = dllName->getRVA();
- e->OrdinalBase = 0;
- e->AddressTableEntries = maxOrdinal + 1;
+ e->OrdinalBase = 1;
+ e->AddressTableEntries = maxOrdinal;
e->NumberOfNamePointers = nameTabSize;
e->ExportAddressTableRVA = addressTab->getRVA();
e->NamePointerRVA = nameTab->getRVA();
class AddressTableChunk : public NonSectionChunk {
public:
- explicit AddressTableChunk(size_t maxOrdinal) : size(maxOrdinal + 1) {}
+ explicit AddressTableChunk(COFFLinkerContext &ctx, size_t maxOrdinal)
+ : size(maxOrdinal), ctx(ctx) {}
size_t getSize() const override { return size * 4; }
void writeTo(uint8_t *buf) const override {
memset(buf, 0, getSize());
- for (const Export &e : config->exports) {
- uint8_t *p = buf + e.ordinal * 4;
+ for (const Export &e : ctx.config.exports) {
+ assert(e.ordinal != 0 && "Export symbol has invalid ordinal");
+ // OrdinalBase is 1, so subtract 1 to get the index.
+ uint8_t *p = buf + (e.ordinal - 1) * 4;
uint32_t bit = 0;
// Pointer to thumb code must have the LSB set, so adjust it.
- if (config->machine == ARMNT && !e.data)
+ if (ctx.config.machine == ARMNT && !e.data)
bit = 1;
if (e.forwardChunk) {
write32le(p, e.forwardChunk->getRVA() | bit);
private:
size_t size;
+ const COFFLinkerContext &ctx;
};
class NamePointersChunk : public NonSectionChunk {
class ExportOrdinalChunk : public NonSectionChunk {
public:
- explicit ExportOrdinalChunk(size_t i) : size(i) {}
+ explicit ExportOrdinalChunk(const COFFLinkerContext &ctx, size_t i)
+ : size(i), ctx(ctx) {}
size_t getSize() const override { return size * 2; }
void writeTo(uint8_t *buf) const override {
- for (Export &e : config->exports) {
+ for (const Export &e : ctx.config.exports) {
if (e.noname)
continue;
- write16le(buf, e.ordinal);
+ assert(e.ordinal != 0 && "Export symbol has invalid ordinal");
+ // This table stores unbiased indices, so subtract 1 (OrdinalBase).
+ write16le(buf, e.ordinal - 1);
buf += 2;
}
}
private:
size_t size;
+ const COFFLinkerContext &ctx;
};
} // anonymous namespace
-void IdataContents::create() {
- std::vector<std::vector<DefinedImportData *>> v = binImports(imports);
+void IdataContents::create(COFFLinkerContext &ctx) {
+ std::vector<std::vector<DefinedImportData *>> v = binImports(ctx, imports);
// Create .idata contents for each DLL.
for (std::vector<DefinedImportData *> &syms : v) {
for (DefinedImportData *s : syms) {
uint16_t ord = s->getOrdinal();
if (s->getExternalName().empty()) {
- lookups.push_back(make<OrdinalOnlyChunk>(ord));
- addresses.push_back(make<OrdinalOnlyChunk>(ord));
+ lookups.push_back(make<OrdinalOnlyChunk>(ctx, ord));
+ addresses.push_back(make<OrdinalOnlyChunk>(ctx, ord));
continue;
}
auto *c = make<HintNameChunk>(s->getExternalName(), ord);
- lookups.push_back(make<LookupChunk>(c));
- addresses.push_back(make<LookupChunk>(c));
+ lookups.push_back(make<LookupChunk>(ctx, c));
+ addresses.push_back(make<LookupChunk>(ctx, c));
hints.push_back(c);
}
// Terminate with null values.
- lookups.push_back(make<NullChunk>(config->wordsize));
- addresses.push_back(make<NullChunk>(config->wordsize));
+ lookups.push_back(make<NullChunk>(ctx.config.wordsize));
+ addresses.push_back(make<NullChunk>(ctx.config.wordsize));
for (int i = 0, e = syms.size(); i < e; ++i)
syms[i]->setLocation(addresses[base + i]);
void DelayLoadContents::create(Defined *h) {
helper = h;
- std::vector<std::vector<DefinedImportData *>> v = binImports(imports);
+ std::vector<std::vector<DefinedImportData *>> v = binImports(ctx, imports);
+
+ Chunk *unwind = newTailMergeUnwindInfoChunk();
// Create .didat contents for each DLL.
for (std::vector<DefinedImportData *> &syms : v) {
size_t base = addresses.size();
Chunk *tm = newTailMergeChunk(dir);
+ Chunk *pdataChunk = unwind ? newTailMergePDataChunk(tm, unwind) : nullptr;
for (DefinedImportData *s : syms) {
Chunk *t = newThunkChunk(s, tm);
- auto *a = make<DelayAddressChunk>(t);
+ auto *a = make<DelayAddressChunk>(ctx, t);
addresses.push_back(a);
thunks.push_back(t);
StringRef extName = s->getExternalName();
if (extName.empty()) {
- names.push_back(make<OrdinalOnlyChunk>(s->getOrdinal()));
+ names.push_back(make<OrdinalOnlyChunk>(ctx, s->getOrdinal()));
} else {
auto *c = make<HintNameChunk>(extName, 0);
- names.push_back(make<LookupChunk>(c));
+ names.push_back(make<LookupChunk>(ctx, c));
hintNames.push_back(c);
- // Add a syntentic symbol for this load thunk, using the "__imp_load"
+ // Add a synthetic symbol for this load thunk, using the "__imp___load"
// prefix, in case this thunk needs to be added to the list of valid
// call targets for Control Flow Guard.
- StringRef symName = saver.save("__imp_load_" + extName);
+ StringRef symName = saver().save("__imp___load_" + extName);
s->loadThunkSym =
- cast<DefinedSynthetic>(symtab->addSynthetic(symName, t));
+ cast<DefinedSynthetic>(ctx.symtab.addSynthetic(symName, t));
}
}
thunks.push_back(tm);
+ if (pdataChunk)
+ pdata.push_back(pdataChunk);
StringRef tmName =
- saver.save("__tailMerge_" + syms[0]->getDLLName().lower());
- symtab->addSynthetic(tmName, tm);
+ saver().save("__tailMerge_" + syms[0]->getDLLName().lower());
+ ctx.symtab.addSynthetic(tmName, tm);
// Terminate with null values.
addresses.push_back(make<NullChunk>(8));
names.push_back(make<NullChunk>(8));
dir->nameTab = names[base];
dirs.push_back(dir);
}
+
+ if (unwind)
+ unwindinfo.push_back(unwind);
// Add null terminator.
dirs.push_back(make<NullChunk>(sizeof(delay_import_directory_table_entry)));
}
Chunk *DelayLoadContents::newTailMergeChunk(Chunk *dir) {
- switch (config->machine) {
+ switch (ctx.config.machine) {
case AMD64:
return make<TailMergeChunkX64>(dir, helper);
case I386:
- return make<TailMergeChunkX86>(dir, helper);
+ return make<TailMergeChunkX86>(ctx, dir, helper);
case ARMNT:
- return make<TailMergeChunkARM>(dir, helper);
+ return make<TailMergeChunkARM>(ctx, dir, helper);
case ARM64:
return make<TailMergeChunkARM64>(dir, helper);
default:
}
}
+Chunk *DelayLoadContents::newTailMergeUnwindInfoChunk() {
+ switch (ctx.config.machine) {
+ case AMD64:
+ return make<TailMergeUnwindInfoX64>();
+ // FIXME: Add support for other architectures.
+ default:
+ return nullptr; // Just don't generate unwind info.
+ }
+}
+Chunk *DelayLoadContents::newTailMergePDataChunk(Chunk *tm, Chunk *unwind) {
+ switch (ctx.config.machine) {
+ case AMD64:
+ return make<TailMergePDataChunkX64>(tm, unwind);
+ // FIXME: Add support for other architectures.
+ default:
+ return nullptr; // Just don't generate unwind info.
+ }
+}
+
Chunk *DelayLoadContents::newThunkChunk(DefinedImportData *s,
Chunk *tailMerge) {
- switch (config->machine) {
+ switch (ctx.config.machine) {
case AMD64:
return make<ThunkChunkX64>(s, tailMerge);
case I386:
- return make<ThunkChunkX86>(s, tailMerge);
+ return make<ThunkChunkX86>(ctx, s, tailMerge);
case ARMNT:
- return make<ThunkChunkARM>(s, tailMerge);
+ return make<ThunkChunkARM>(ctx, s, tailMerge);
case ARM64:
return make<ThunkChunkARM64>(s, tailMerge);
default:
}
}
-EdataContents::EdataContents() {
+EdataContents::EdataContents(COFFLinkerContext &ctx) : ctx(ctx) {
uint16_t maxOrdinal = 0;
- for (Export &e : config->exports)
+ for (Export &e : ctx.config.exports)
maxOrdinal = std::max(maxOrdinal, e.ordinal);
- auto *dllName = make<StringChunk>(sys::path::filename(config->outputFile));
- auto *addressTab = make<AddressTableChunk>(maxOrdinal);
+ auto *dllName = make<StringChunk>(sys::path::filename(ctx.config.outputFile));
+ auto *addressTab = make<AddressTableChunk>(ctx, maxOrdinal);
std::vector<Chunk *> names;
- for (Export &e : config->exports)
+ for (Export &e : ctx.config.exports)
if (!e.noname)
names.push_back(make<StringChunk>(e.exportName));
std::vector<Chunk *> forwards;
- for (Export &e : config->exports) {
+ for (Export &e : ctx.config.exports) {
if (e.forwardTo.empty())
continue;
e.forwardChunk = make<StringChunk>(e.forwardTo);
}
auto *nameTab = make<NamePointersChunk>(names);
- auto *ordinalTab = make<ExportOrdinalChunk>(names.size());
+ auto *ordinalTab = make<ExportOrdinalChunk>(ctx, names.size());
auto *dir = make<ExportDirectoryChunk>(maxOrdinal, names.size(), dllName,
addressTab, nameTab, ordinalTab);
chunks.push_back(dir);
chunks.insert(chunks.end(), forwards.begin(), forwards.end());
}
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#include "Chunks.h"
#include "Symbols.h"
-namespace lld {
-namespace coff {
+namespace lld::coff {
// Windows-specific.
// IdataContents creates all chunks for the DLL import table.
void add(DefinedImportData *sym) { imports.push_back(sym); }
bool empty() { return imports.empty(); }
- void create();
+ void create(COFFLinkerContext &ctx);
std::vector<DefinedImportData *> imports;
std::vector<Chunk *> dirs;
// DelayLoadContents creates all chunks for the delay-load DLL import table.
class DelayLoadContents {
public:
+ DelayLoadContents(COFFLinkerContext &ctx) : ctx(ctx) {}
void add(DefinedImportData *sym) { imports.push_back(sym); }
bool empty() { return imports.empty(); }
void create(Defined *helper);
std::vector<Chunk *> getChunks();
std::vector<Chunk *> getDataChunks();
ArrayRef<Chunk *> getCodeChunks() { return thunks; }
+ ArrayRef<Chunk *> getCodePData() { return pdata; }
+ ArrayRef<Chunk *> getCodeUnwindInfo() { return unwindinfo; }
uint64_t getDirRVA() { return dirs[0]->getRVA(); }
uint64_t getDirSize();
private:
Chunk *newThunkChunk(DefinedImportData *s, Chunk *tailMerge);
Chunk *newTailMergeChunk(Chunk *dir);
+ Chunk *newTailMergePDataChunk(Chunk *tm, Chunk *unwind);
+ Chunk *newTailMergeUnwindInfoChunk();
Defined *helper;
std::vector<DefinedImportData *> imports;
std::vector<Chunk *> names;
std::vector<Chunk *> hintNames;
std::vector<Chunk *> thunks;
+ std::vector<Chunk *> pdata;
+ std::vector<Chunk *> unwindinfo;
std::vector<Chunk *> dllNames;
+
+ COFFLinkerContext &ctx;
};
// Windows-specific.
// EdataContents creates all chunks for the DLL export table.
class EdataContents {
public:
- EdataContents();
+ EdataContents(COFFLinkerContext &ctx);
std::vector<Chunk *> chunks;
uint64_t getRVA() { return chunks[0]->getRVA(); }
uint64_t getSize() {
return chunks.back()->getRVA() + chunks.back()->getSize() - getRVA();
}
+
+ COFFLinkerContext &ctx;
};
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
//===----------------------------------------------------------------------===//
#include "DebugTypes.h"
+#include "COFFLinkerContext.h"
#include "Chunks.h"
#include "Driver.h"
#include "InputFiles.h"
#include "TypeMerger.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
-#include "lld/Common/Timer.h"
#include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h"
#include "llvm/DebugInfo/CodeView/TypeRecord.h"
#include "llvm/DebugInfo/CodeView/TypeRecordHelpers.h"
// before any dependent OBJ.
class TypeServerSource : public TpiSource {
public:
- explicit TypeServerSource(PDBInputFile *f)
- : TpiSource(PDB, nullptr), pdbInputFile(f) {
- if (f->loadErr && *f->loadErr)
+ explicit TypeServerSource(COFFLinkerContext &ctx, PDBInputFile *f)
+ : TpiSource(ctx, PDB, nullptr), pdbInputFile(f) {
+ if (f->loadErrorStr)
return;
pdb::PDBFile &file = f->session->getPDBFile();
auto expectedInfo = file.getPDBInfoStream();
if (!expectedInfo)
return;
Guid = expectedInfo->getGuid();
- auto it = mappings.emplace(Guid, this);
- assert(it.second);
- (void)it;
+ auto it = ctx.typeServerSourceMappings.emplace(Guid, this);
+ if (!it.second) {
+ // If we hit here we have collision on Guid's in two PDB files.
+ // This can happen if the PDB Guid is invalid or if we are really
+ // unlucky. This should fall back on stright file-system lookup.
+ it.first->second = nullptr;
+ }
}
Error mergeDebugT(TypeMerger *m) override;
// The PDB signature GUID.
codeview::GUID Guid;
-
- static std::map<codeview::GUID, TypeServerSource *> mappings;
};
// Companion to TypeServerSource. Stores the index map for the IPI stream in the
// invariant of one type index space per source.
class TypeServerIpiSource : public TpiSource {
public:
- explicit TypeServerIpiSource() : TpiSource(PDBIpi, nullptr) {}
+ explicit TypeServerIpiSource(COFFLinkerContext &ctx)
+ : TpiSource(ctx, PDBIpi, nullptr) {}
friend class TypeServerSource;
Expected<TypeServerSource *> getTypeServerSource();
public:
- UseTypeServerSource(ObjFile *f, TypeServer2Record ts)
- : TpiSource(UsingPDB, f), typeServerDependency(ts) {}
+ UseTypeServerSource(COFFLinkerContext &ctx, ObjFile *f, TypeServer2Record ts)
+ : TpiSource(ctx, UsingPDB, f), typeServerDependency(ts) {}
Error mergeDebugT(TypeMerger *m) override;
// such files, clang does not.
class PrecompSource : public TpiSource {
public:
- PrecompSource(ObjFile *f) : TpiSource(PCH, f) {
- if (!f->pchSignature || !*f->pchSignature)
- fatal(toString(f) +
- " claims to be a PCH object, but does not have a valid signature");
- auto it = mappings.emplace(*f->pchSignature, this);
- if (!it.second)
- fatal("a PCH object with the same signature has already been provided (" +
- toString(it.first->second->file) + " and " + toString(file) + ")");
+ PrecompSource(COFFLinkerContext &ctx, ObjFile *f) : TpiSource(ctx, PCH, f) {
+ // If the S_OBJNAME record contains the PCH signature, we'll register this
+ // source file right away.
+ registerMapping();
}
+ Error mergeDebugT(TypeMerger *m) override;
+
void loadGHashes() override;
bool isDependency() const override { return true; }
- static std::map<uint32_t, PrecompSource *> mappings;
+private:
+ void registerMapping();
+
+ // Whether this precomp OBJ was recorded in the precompSourceMappings map.
+ // Only happens if the file->pchSignature is valid.
+ bool registered = false;
};
// This class represents the debug type stream of an OBJ file that depends on a
// Microsoft precompiled headers OBJ (see PrecompSource).
class UsePrecompSource : public TpiSource {
public:
- UsePrecompSource(ObjFile *f, PrecompRecord precomp)
- : TpiSource(UsingPCH, f), precompDependency(precomp) {}
+ UsePrecompSource(COFFLinkerContext &ctx, ObjFile *f, PrecompRecord precomp)
+ : TpiSource(ctx, UsingPCH, f), precompDependency(precomp) {}
Error mergeDebugT(TypeMerger *m) override;
private:
Error mergeInPrecompHeaderObj();
+ PrecompSource *findObjByName(StringRef fileNameOnly);
+ PrecompSource *findPrecompSource(ObjFile *file, PrecompRecord &pr);
+ Expected<PrecompSource *> findPrecompMap(ObjFile *file, PrecompRecord &pr);
+
public:
// Information about the Precomp OBJ dependency, that needs to be loaded in
// before merging this OBJ.
};
} // namespace
-std::vector<TpiSource *> TpiSource::instances;
-ArrayRef<TpiSource *> TpiSource::dependencySources;
-ArrayRef<TpiSource *> TpiSource::objectSources;
-
-TpiSource::TpiSource(TpiKind k, ObjFile *f)
- : kind(k), tpiSrcIdx(instances.size()), file(f) {
- instances.push_back(this);
+TpiSource::TpiSource(COFFLinkerContext &ctx, TpiKind k, ObjFile *f)
+ : ctx(ctx), kind(k), tpiSrcIdx(ctx.tpiSourceList.size()), file(f) {
+ ctx.addTpiSource(this);
}
// Vtable key method.
consumeError(std::move(typeMergingError));
}
-void TpiSource::sortDependencies() {
- // Order dependencies first, but preserve the existing order.
- std::vector<TpiSource *> deps;
- std::vector<TpiSource *> objs;
- for (TpiSource *s : instances)
- (s->isDependency() ? deps : objs).push_back(s);
- uint32_t numDeps = deps.size();
- uint32_t numObjs = objs.size();
- instances = std::move(deps);
- instances.insert(instances.end(), objs.begin(), objs.end());
- for (uint32_t i = 0, e = instances.size(); i < e; ++i)
- instances[i]->tpiSrcIdx = i;
- dependencySources = makeArrayRef(instances.data(), numDeps);
- objectSources = makeArrayRef(instances.data() + numDeps, numObjs);
-}
-
-TpiSource *lld::coff::makeTpiSource(ObjFile *file) {
- return make<TpiSource>(TpiSource::Regular, file);
+TpiSource *lld::coff::makeTpiSource(COFFLinkerContext &ctx, ObjFile *file) {
+ return make<TpiSource>(ctx, TpiSource::Regular, file);
}
-TpiSource *lld::coff::makeTypeServerSource(PDBInputFile *pdbInputFile) {
+TpiSource *lld::coff::makeTypeServerSource(COFFLinkerContext &ctx,
+ PDBInputFile *pdbInputFile) {
// Type server sources come in pairs: the TPI stream, and the IPI stream.
- auto *tpiSource = make<TypeServerSource>(pdbInputFile);
+ auto *tpiSource = make<TypeServerSource>(ctx, pdbInputFile);
if (pdbInputFile->session->getPDBFile().hasPDBIpiStream())
- tpiSource->ipiSrc = make<TypeServerIpiSource>();
+ tpiSource->ipiSrc = make<TypeServerIpiSource>(ctx);
return tpiSource;
}
-TpiSource *lld::coff::makeUseTypeServerSource(ObjFile *file,
+TpiSource *lld::coff::makeUseTypeServerSource(COFFLinkerContext &ctx,
+ ObjFile *file,
TypeServer2Record ts) {
- return make<UseTypeServerSource>(file, ts);
+ return make<UseTypeServerSource>(ctx, file, ts);
}
-TpiSource *lld::coff::makePrecompSource(ObjFile *file) {
- return make<PrecompSource>(file);
+TpiSource *lld::coff::makePrecompSource(COFFLinkerContext &ctx, ObjFile *file) {
+ return make<PrecompSource>(ctx, file);
}
-TpiSource *lld::coff::makeUsePrecompSource(ObjFile *file,
+TpiSource *lld::coff::makeUsePrecompSource(COFFLinkerContext &ctx,
+ ObjFile *file,
PrecompRecord precomp) {
- return make<UsePrecompSource>(file, precomp);
+ return make<UsePrecompSource>(ctx, file, precomp);
}
-std::map<codeview::GUID, TypeServerSource *> TypeServerSource::mappings;
-
-std::map<uint32_t, PrecompSource *> PrecompSource::mappings;
-
bool TpiSource::remapTypeIndex(TypeIndex &ti, TiRefKind refKind) const {
if (ti.isSimple())
return true;
reinterpret_cast<TypeIndex *>(contents.data() + ref.Offset), ref.Count);
for (TypeIndex &ti : indices) {
if (!remapTypeIndex(ti, ref.Kind)) {
- if (config->verbose) {
+ if (ctx.config.verbose) {
uint16_t kind =
reinterpret_cast<const RecordPrefix *>(rec.data())->RecordKind;
StringRef fname = file ? file->getName() : "<unknown PDB>";
debugH = debugH.drop_front(sizeof(object::debug_h_header));
return header->Magic == COFF::DEBUG_HASHES_SECTION_MAGIC &&
header->Version == 0 &&
- header->HashAlgorithm == uint16_t(GlobalTypeHashAlg::SHA1_8) &&
+ header->HashAlgorithm == uint16_t(GlobalTypeHashAlg::BLAKE3) &&
(debugH.size() % 8 == 0);
}
-static Optional<ArrayRef<uint8_t>> getDebugH(ObjFile *file) {
+static std::optional<ArrayRef<uint8_t>> getDebugH(ObjFile *file) {
SectionChunk *sec =
SectionChunk::findByName(file->getDebugChunks(), ".debug$H");
if (!sec)
- return llvm::None;
+ return std::nullopt;
ArrayRef<uint8_t> contents = sec->getContents();
if (!canUseDebugH(contents))
- return None;
+ return std::nullopt;
return contents;
}
// Merge .debug$T for a generic object file.
Error TpiSource::mergeDebugT(TypeMerger *m) {
- assert(!config->debugGHashes &&
+ assert(!ctx.config.debugGHashes &&
"use remapTpiWithGHashes when ghash is enabled");
CVTypeArray types;
// When dealing with PCH.OBJ, some indices were already merged.
unsigned nbHeadIndices = indexMapStorage.size();
- if (auto err = mergeTypeAndIdRecords(
- m->idTable, m->typeTable, indexMapStorage, types, file->pchSignature))
+ std::optional<PCHMergerInfo> pchInfo;
+ if (auto err = mergeTypeAndIdRecords(m->idTable, m->typeTable,
+ indexMapStorage, types, pchInfo))
fatal("codeview::mergeTypeAndIdRecords failed: " +
toString(std::move(err)));
+ if (pchInfo) {
+ file->pchSignature = pchInfo->PCHSignature;
+ endPrecompIdx = pchInfo->EndPrecompIndex;
+ }
// In an object, there is only one mapping for both types and items.
tpiMap = indexMapStorage;
ipiMap = indexMapStorage;
- if (config->showSummary) {
+ if (ctx.config.showSummary) {
nbTypeRecords = indexMapStorage.size() - nbHeadIndices;
nbTypeRecordsBytes = reader.getLength();
// Count how many times we saw each type record in our input. This
m->tpiCounts.resize(m->getTypeTable().size());
m->ipiCounts.resize(m->getIDTable().size());
uint32_t srcIdx = nbHeadIndices;
- for (CVType &ty : types) {
+ for (const CVType &ty : types) {
TypeIndex dstIdx = tpiMap[srcIdx++];
// Type merging may fail, so a complex source type may become the simple
// NotTranslated type, which cannot be used as an array index.
// Merge types from a type server PDB.
Error TypeServerSource::mergeDebugT(TypeMerger *m) {
- assert(!config->debugGHashes &&
+ assert(!ctx.config.debugGHashes &&
"use remapTpiWithGHashes when ghash is enabled");
pdb::PDBFile &pdbFile = pdbInputFile->session->getPDBFile();
ipiMap = ipiSrc->indexMapStorage;
}
- if (config->showSummary) {
+ if (ctx.config.showSummary) {
nbTypeRecords = tpiMap.size() + ipiMap.size();
nbTypeRecordsBytes =
expectedTpi->typeArray().getUnderlyingStream().getLength() +
const codeview::GUID &tsId = typeServerDependency.getGuid();
StringRef tsPath = typeServerDependency.getName();
- TypeServerSource *tsSrc;
- auto it = TypeServerSource::mappings.find(tsId);
- if (it != TypeServerSource::mappings.end()) {
- tsSrc = it->second;
- } else {
+ TypeServerSource *tsSrc = nullptr;
+ auto it = ctx.typeServerSourceMappings.find(tsId);
+ if (it != ctx.typeServerSourceMappings.end()) {
+ tsSrc = (TypeServerSource *)it->second;
+ }
+ if (tsSrc == nullptr) {
// The file failed to load, lookup by name
- PDBInputFile *pdb = PDBInputFile::findFromRecordPath(tsPath, file);
+ PDBInputFile *pdb = PDBInputFile::findFromRecordPath(ctx, tsPath, file);
if (!pdb)
return createFileError(tsPath, errorCodeToError(std::error_code(
ENOENT, std::generic_category())));
// If an error occurred during loading, throw it now
- if (pdb->loadErr && *pdb->loadErr)
- return createFileError(tsPath, std::move(*pdb->loadErr));
+ if (pdb->loadErrorStr)
+ return createFileError(
+ tsPath, make_error<StringError>(*pdb->loadErrorStr,
+ llvm::inconvertibleErrorCode()));
tsSrc = (TypeServerSource *)pdb->debugTypesObj;
}
// Find by name an OBJ provided on the command line
-static PrecompSource *findObjByName(StringRef fileNameOnly) {
+PrecompSource *UsePrecompSource::findObjByName(StringRef fileNameOnly) {
SmallString<128> currentPath;
- for (auto kv : PrecompSource::mappings) {
+ for (auto kv : ctx.precompSourceMappings) {
StringRef currentFileName = sys::path::filename(kv.second->file->getName(),
sys::path::Style::windows);
// Compare based solely on the file name (link.exe behavior)
if (equalsPath(currentFileName, fileNameOnly))
- return kv.second;
+ return (PrecompSource *)kv.second;
}
return nullptr;
}
-static PrecompSource *findPrecompSource(ObjFile *file, PrecompRecord &pr) {
+PrecompSource *UsePrecompSource::findPrecompSource(ObjFile *file,
+ PrecompRecord &pr) {
// Cross-compile warning: given that Clang doesn't generate LF_PRECOMP
// records, we assume the OBJ comes from a Windows build of cl.exe. Thusly,
// the paths embedded in the OBJs are in the Windows format.
SmallString<128> prFileName =
sys::path::filename(pr.getPrecompFilePath(), sys::path::Style::windows);
- auto it = PrecompSource::mappings.find(pr.getSignature());
- if (it != PrecompSource::mappings.end()) {
- return it->second;
+ auto it = ctx.precompSourceMappings.find(pr.getSignature());
+ if (it != ctx.precompSourceMappings.end()) {
+ return (PrecompSource *)it->second;
}
// Lookup by name
return findObjByName(prFileName);
}
-static Expected<PrecompSource *> findPrecompMap(ObjFile *file,
- PrecompRecord &pr) {
+Expected<PrecompSource *> UsePrecompSource::findPrecompMap(ObjFile *file,
+ PrecompRecord &pr) {
PrecompSource *precomp = findPrecompSource(file, pr);
if (!precomp)
pr.getPrecompFilePath(),
make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch));
- if (pr.getSignature() != file->pchSignature)
+ // Don't rely on the PCH signature to validate the concordance between the PCH
+ // and the OBJ that uses it. However we do validate here that the
+ // LF_ENDPRECOMP record index lines up with the number of type records
+ // LF_PRECOMP is expecting.
+ if (precomp->endPrecompIdx != pr.getTypesCount())
return createFileError(
toString(file),
make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch));
- if (pr.getSignature() != *precomp->file->pchSignature)
- return createFileError(
- toString(precomp->file),
- make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch));
-
return precomp;
}
return TpiSource::mergeDebugT(m);
}
-uint32_t TpiSource::countTypeServerPDBs() {
- return TypeServerSource::mappings.size();
-}
+Error PrecompSource::mergeDebugT(TypeMerger *m) {
+ // In some cases, the S_OBJNAME record doesn't contain the PCH signature.
+ // The signature comes later with the LF_ENDPRECOMP record, so we first need
+ // to merge in all the .PCH.OBJ file type records, before registering below.
+ if (Error e = TpiSource::mergeDebugT(m))
+ return e;
+
+ registerMapping();
-uint32_t TpiSource::countPrecompObjs() {
- return PrecompSource::mappings.size();
+ return Error::success();
}
-void TpiSource::clear() {
- // Clean up any owned ghash allocations.
- clearGHashes();
- TpiSource::instances.clear();
- TypeServerSource::mappings.clear();
- PrecompSource::mappings.clear();
+void PrecompSource::registerMapping() {
+ if (registered)
+ return;
+ if (file->pchSignature && *file->pchSignature) {
+ auto it = ctx.precompSourceMappings.emplace(*file->pchSignature, this);
+ if (!it.second)
+ fatal("a PCH object with the same signature has already been provided (" +
+ toString(it.first->second->file) + " and " + toString(file) + ")");
+ registered = true;
+ }
}
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
void TpiSource::loadGHashes() {
- if (Optional<ArrayRef<uint8_t>> debugH = getDebugH(file)) {
+ if (std::optional<ArrayRef<uint8_t>> debugH = getDebugH(file)) {
ghashes = getHashesFromDebugH(*debugH);
ownedGHashes = false;
} else {
return;
GloballyHashedType *hashes = new GloballyHashedType[hashVec.size()];
memcpy(hashes, hashVec.data(), hashVec.size() * sizeof(GloballyHashedType));
- ghashes = makeArrayRef(hashes, hashVec.size());
+ ghashes = ArrayRef(hashes, hashVec.size());
ownedGHashes = true;
}
size_t offset = merged.recs.size();
size_t newSize = alignTo(ty.length(), 4);
merged.recs.resize(offset + newSize);
- auto newRec = makeMutableArrayRef(&merged.recs[offset], newSize);
+ auto newRec = MutableArrayRef(&merged.recs[offset], newSize);
memcpy(newRec.data(), ty.data().data(), newSize);
// Fix up the record prefix and padding bytes if it required resizing.
TypeIndex beginIndex) {
// Re-sort the list of unique types by index.
if (kind == PDB)
- assert(std::is_sorted(uniqueTypes.begin(), uniqueTypes.end()));
+ assert(llvm::is_sorted(uniqueTypes));
else
llvm::sort(uniqueTypes);
}
void TpiSource::remapTpiWithGHashes(GHashState *g) {
- assert(config->debugGHashes && "ghashes must be enabled");
+ assert(ctx.config.debugGHashes && "ghashes must be enabled");
fillMapFromGHashes(g);
tpiMap = indexMapStorage;
ipiMap = indexMapStorage;
mergeUniqueTypeRecords(file->debugTypes);
// TODO: Free all unneeded ghash resources now that we have a full index map.
- if (config->showSummary) {
+ if (ctx.config.showSummary) {
nbTypeRecords = ghashes.size();
nbTypeRecordsBytes = file->debugTypes.size();
}
// Merge types from a type server PDB.
void TypeServerSource::remapTpiWithGHashes(GHashState *g) {
- assert(config->debugGHashes && "ghashes must be enabled");
+ assert(ctx.config.debugGHashes && "ghashes must be enabled");
// IPI merging depends on TPI, so do TPI first, then do IPI. No need to
// propagate errors, those should've been handled during ghash loading.
ipiSrc->ipiMap = ipiMap;
ipiSrc->mergeUniqueTypeRecords(typeArrayToBytes(ipi.typeArray()));
- if (config->showSummary) {
+ if (ctx.config.showSummary) {
nbTypeRecords = ipiSrc->ghashes.size();
nbTypeRecordsBytes = ipi.typeArray().getUnderlyingStream().getLength();
}
}
- if (config->showSummary) {
+ if (ctx.config.showSummary) {
nbTypeRecords += ghashes.size();
nbTypeRecordsBytes += tpi.typeArray().getUnderlyingStream().getLength();
}
// Remember the index of the LF_ENDPRECOMP record so it can be excluded from
// the PDB. There must be an entry in the list of ghashes so that the type
// indexes of the following records in the /Yc PCH object line up.
- if (ty.kind() == LF_ENDPRECOMP)
- endPrecompGHashIdx = ghashIdx;
+ if (ty.kind() == LF_ENDPRECOMP) {
+ EndPrecompRecord endPrecomp;
+ cantFail(TypeDeserializer::deserializeAs<EndPrecompRecord>(
+ const_cast<CVType &>(ty), endPrecomp));
+ file->pchSignature = endPrecomp.getSignature();
+ registerMapping();
+ endPrecompIdx = ghashIdx;
+ }
hashVec.push_back(GloballyHashedType::hashType(ty, hashVec, hashVec));
isItemIndex.push_back(isIdRecord(ty.kind()));
}
void UsePrecompSource::loadGHashes() {
- PrecompSource *pchSrc = findPrecompSource(file, precompDependency);
- if (!pchSrc)
+ auto e = findPrecompMap(file, precompDependency);
+ if (!e) {
+ warn(toString(e.takeError()));
return;
+ }
+
+ PrecompSource *pchSrc = *e;
- // To compute ghashes of a /Yu object file, we need to build on the the
- // ghashes of the /Yc PCH object. After we are done hashing, discard the
- // ghashes from the PCH source so we don't unnecessarily try to deduplicate
- // them.
+ // To compute ghashes of a /Yu object file, we need to build on the ghashes of
+ // the /Yc PCH object. After we are done hashing, discard the ghashes from the
+ // PCH source so we don't unnecessarily try to deduplicate them.
std::vector<GloballyHashedType> hashVec =
pchSrc->ghashes.take_front(precompDependency.getTypesCount());
forEachTypeChecked(file->debugTypes, [&](const CVType &ty) {
mergeUniqueTypeRecords(file->debugTypes,
TypeIndex(precompDependency.getStartTypeIndex() +
precompDependency.getTypesCount()));
- if (config->showSummary) {
+ if (ctx.config.showSummary) {
nbTypeRecords = ghashes.size();
nbTypeRecordsBytes = file->debugTypes.size();
}
/// Insert the cell with the given ghash into the table. Return the insertion
/// position in the table. It is safe for the caller to store the insertion
/// position because the table cannot be resized.
- uint32_t insert(GloballyHashedType ghash, GHashCell newCell);
+ uint32_t insert(COFFLinkerContext &ctx, GloballyHashedType ghash,
+ GHashCell newCell);
};
/// A ghash table cell for deduplicating types from TpiSources.
class GHashCell {
- uint64_t data = 0;
+ // Force "data" to be 64-bit aligned; otherwise, some versions of clang
+ // will generate calls to libatomic when using some versions of libstdc++
+ // on 32-bit targets. (Also, in theory, there could be a target where
+ // new[] doesn't always return an 8-byte-aligned allocation.)
+ alignas(sizeof(uint64_t)) uint64_t data = 0;
public:
GHashCell() = default;
bool isItem() const { return data & (1ULL << 63U); }
/// Get the ghash key for this cell.
- GloballyHashedType getGHash() const {
- return TpiSource::instances[getTpiSrcIdx()]->ghashes[getGHashIdx()];
+ GloballyHashedType getGHash(const COFFLinkerContext &ctx) const {
+ return ctx.tpiSourceList[getTpiSrcIdx()]->ghashes[getGHashIdx()];
}
/// The priority function for the cell. The data is stored such that lower
};
} // namespace
-namespace lld {
-namespace coff {
+namespace lld::coff {
/// This type is just a wrapper around GHashTable with external linkage so it
/// can be used from a header.
struct GHashState {
GHashTable table;
};
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
GHashTable::~GHashTable() { delete[] table; }
tableSize = newTableSize;
}
-uint32_t GHashTable::insert(GloballyHashedType ghash, GHashCell newCell) {
+uint32_t GHashTable::insert(COFFLinkerContext &ctx, GloballyHashedType ghash,
+ GHashCell newCell) {
assert(!newCell.isEmpty() && "cannot insert empty cell value");
// FIXME: The low bytes of SHA1 have low entropy for short records, which
// - cell has non-matching key: hash collision, probe next cell
auto *cellPtr = reinterpret_cast<std::atomic<GHashCell> *>(&table[idx]);
GHashCell oldCell(cellPtr->load());
- while (oldCell.isEmpty() || oldCell.getGHash() == ghash) {
+ while (oldCell.isEmpty() || oldCell.getGHash(ctx) == ghash) {
// Check if there is an existing ghash entry with a higher priority
// (earlier ordering). If so, this is a duplicate, we are done.
if (!oldCell.isEmpty() && oldCell < newCell)
llvm_unreachable("left infloop");
}
-TypeMerger::TypeMerger(llvm::BumpPtrAllocator &alloc)
- : typeTable(alloc), idTable(alloc) {}
+TypeMerger::TypeMerger(COFFLinkerContext &c, llvm::BumpPtrAllocator &alloc)
+ : typeTable(alloc), idTable(alloc), ctx(c) {}
TypeMerger::~TypeMerger() = default;
void TypeMerger::mergeTypesWithGHash() {
// Load ghashes. Do type servers and PCH objects first.
{
- ScopedTimer t1(loadGHashTimer);
- parallelForEach(TpiSource::dependencySources,
+ ScopedTimer t1(ctx.loadGHashTimer);
+ parallelForEach(dependencySources,
[&](TpiSource *source) { source->loadGHashes(); });
- parallelForEach(TpiSource::objectSources,
+ parallelForEach(objectSources,
[&](TpiSource *source) { source->loadGHashes(); });
}
- ScopedTimer t2(mergeGHashTimer);
+ ScopedTimer t2(ctx.mergeGHashTimer);
GHashState ghashState;
// Estimate the size of hash table needed to deduplicate ghashes. This *must*
// small compared to total memory usage, at eight bytes per input type record,
// and most input type records are larger than eight bytes.
size_t tableSize = 0;
- for (TpiSource *source : TpiSource::instances)
+ for (TpiSource *source : ctx.tpiSourceList)
tableSize += source->ghashes.size();
// Cap the table size so that we can use 32-bit cell indices. Type indices are
// position. Because the table does not rehash, the position will not change
// under insertion. After insertion is done, the value of the cell can be read
// to retrieve the final PDB type index.
- parallelForEachN(0, TpiSource::instances.size(), [&](size_t tpiSrcIdx) {
- TpiSource *source = TpiSource::instances[tpiSrcIdx];
+ parallelFor(0, ctx.tpiSourceList.size(), [&](size_t tpiSrcIdx) {
+ TpiSource *source = ctx.tpiSourceList[tpiSrcIdx];
source->indexMapStorage.resize(source->ghashes.size());
for (uint32_t i = 0, e = source->ghashes.size(); i < e; i++) {
if (source->shouldOmitFromPdb(i)) {
GloballyHashedType ghash = source->ghashes[i];
bool isItem = source->isItemIndex.test(i);
uint32_t cellIdx =
- ghashState.table.insert(ghash, GHashCell(isItem, tpiSrcIdx, i));
+ ghashState.table.insert(ctx, ghash, GHashCell(isItem, tpiSrcIdx, i));
// Store the ghash cell index as a type index in indexMapStorage. Later
// we will replace it with the PDB type index.
// - source 0, type 1...
// - source 1, type 0...
std::vector<GHashCell> entries;
- for (const GHashCell &cell :
- makeArrayRef(ghashState.table.table, tableSize)) {
+ for (const GHashCell &cell : ArrayRef(ghashState.table.table, tableSize)) {
if (!cell.isEmpty())
entries.push_back(cell);
}
entries.size(), tableSize));
// Find out how many type and item indices there are.
- auto mid =
- std::lower_bound(entries.begin(), entries.end(), GHashCell(true, 0, 0));
+ auto mid = llvm::lower_bound(entries, GHashCell(true, 0, 0));
assert((mid == entries.end() || mid->isItem()) &&
(mid == entries.begin() || !std::prev(mid)->isItem()) &&
"midpoint is not midpoint");
for (uint32_t i = 0, e = entries.size(); i < e; ++i) {
auto &cell = entries[i];
uint32_t tpiSrcIdx = cell.getTpiSrcIdx();
- TpiSource *source = TpiSource::instances[tpiSrcIdx];
+ TpiSource *source = ctx.tpiSourceList[tpiSrcIdx];
source->uniqueTypes.push_back(cell.getGHashIdx());
// Update the ghash table to store the destination PDB type index in the
}
// In parallel, remap all types.
- for_each(TpiSource::dependencySources, [&](TpiSource *source) {
+ for (TpiSource *source : dependencySources)
source->remapTpiWithGHashes(&ghashState);
- });
- parallelForEach(TpiSource::objectSources, [&](TpiSource *source) {
+ parallelForEach(objectSources, [&](TpiSource *source) {
source->remapTpiWithGHashes(&ghashState);
});
// Build a global map of from function ID to function type.
- for (TpiSource *source : TpiSource::instances) {
+ for (TpiSource *source : ctx.tpiSourceList) {
for (auto idToType : source->funcIdToType)
funcIdToType.insert(idToType);
source->funcIdToType.clear();
}
- TpiSource::clearGHashes();
+ clearGHashes();
+}
+
+void TypeMerger::sortDependencies() {
+ // Order dependencies first, but preserve the existing order.
+ std::vector<TpiSource *> deps;
+ std::vector<TpiSource *> objs;
+ for (TpiSource *s : ctx.tpiSourceList)
+ (s->isDependency() ? deps : objs).push_back(s);
+ uint32_t numDeps = deps.size();
+ uint32_t numObjs = objs.size();
+ ctx.tpiSourceList = std::move(deps);
+ ctx.tpiSourceList.insert(ctx.tpiSourceList.end(), objs.begin(), objs.end());
+ for (uint32_t i = 0, e = ctx.tpiSourceList.size(); i < e; ++i)
+ ctx.tpiSourceList[i]->tpiSrcIdx = i;
+ dependencySources = ArrayRef(ctx.tpiSourceList.data(), numDeps);
+ objectSources = ArrayRef(ctx.tpiSourceList.data() + numDeps, numObjs);
}
/// Given the index into the ghash table for a particular type, return the type
return TypeIndex::fromArrayIndex(cell.getGHashIdx());
}
+/// Free heap allocated ghashes.
+void TypeMerger::clearGHashes() {
+ for (TpiSource *src : ctx.tpiSourceList) {
+ if (src->ownedGHashes)
+ delete[] src->ghashes.data();
+ src->ghashes = {};
+ src->isItemIndex.clear();
+ src->uniqueTypes.clear();
+ }
+}
+
// Fill in a TPI or IPI index map using ghashes. For each source type, use its
// ghash to lookup its final type index in the PDB, and store that in the map.
void TpiSource::fillMapFromGHashes(GHashState *g) {
loadPdbTypeIndexFromCell(g, fakeCellIndex.toArrayIndex());
}
}
-
-void TpiSource::clearGHashes() {
- for (TpiSource *src : TpiSource::instances) {
- if (src->ownedGHashes)
- delete[] src->ghashes.data();
- src->ghashes = {};
- src->isItemIndex.clear();
- src->uniqueTypes.clear();
- }
-}
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"
-namespace llvm {
-namespace codeview {
+namespace llvm::codeview {
struct GloballyHashedType;
-} // namespace codeview
-namespace pdb {
+}
+namespace llvm::pdb {
class NativeSession;
class TpiStream;
}
-} // namespace llvm
-namespace lld {
-namespace coff {
+namespace lld::coff {
using llvm::codeview::GloballyHashedType;
using llvm::codeview::TypeIndex;
class PDBInputFile;
class TypeMerger;
struct GHashState;
+class COFFLinkerContext;
class TpiSource {
public:
enum TpiKind : uint8_t { Regular, PCH, UsingPCH, PDB, PDBIpi, UsingPDB };
- TpiSource(TpiKind k, ObjFile *f);
+ TpiSource(COFFLinkerContext &ctx, TpiKind k, ObjFile *f);
virtual ~TpiSource();
/// Produce a mapping from the type and item indices used in the object
// Walk over file->debugTypes and fill in the isItemIndex bit vector.
void fillIsItemIndexFromDebugT();
+ COFFLinkerContext &ctx;
+
public:
bool remapTypesInSymbolRecord(MutableArrayRef<uint8_t> rec);
/// it is unique. This prevents a record from being added to the input ghash
/// table.
bool shouldOmitFromPdb(uint32_t ghashIdx) {
- return ghashIdx == endPrecompGHashIdx;
+ return ghashIdx == endPrecompIdx;
}
- /// All sources of type information in the program.
- static std::vector<TpiSource *> instances;
-
- /// Dependency type sources, such as type servers or PCH object files. These
- /// must be processed before objects that rely on them. Set by
- /// TpiSources::sortDependencies.
- static ArrayRef<TpiSource *> dependencySources;
-
- /// Object file sources. These must be processed after dependencySources.
- static ArrayRef<TpiSource *> objectSources;
-
- /// Sorts the dependencies and reassigns TpiSource indices.
- static void sortDependencies();
-
- static uint32_t countTypeServerPDBs();
- static uint32_t countPrecompObjs();
-
- /// Free heap allocated ghashes.
- static void clearGHashes();
-
- /// Clear global data structures for TpiSources.
- static void clear();
-
const TpiKind kind;
bool ownedGHashes = true;
uint32_t tpiSrcIdx = 0;
-protected:
- /// The ghash index (zero based, not 0x1000-based) of the LF_ENDPRECOMP record
- /// in this object, if one exists. This is the all ones value otherwise. It is
- /// recorded here so that it can be omitted from the final ghash table.
- uint32_t endPrecompGHashIdx = ~0U;
+ /// The index (zero based, not 0x1000-based) of the LF_ENDPRECOMP record in
+ /// this object, if one exists. This is the all ones value otherwise. It is
+ /// recorded here for validation, and so that it can be omitted from the final
+ /// ghash table.
+ uint32_t endPrecompIdx = ~0U;
public:
ObjFile *file;
uint64_t nbTypeRecordsBytes = 0;
};
-TpiSource *makeTpiSource(ObjFile *file);
-TpiSource *makeTypeServerSource(PDBInputFile *pdbInputFile);
-TpiSource *makeUseTypeServerSource(ObjFile *file,
+TpiSource *makeTpiSource(COFFLinkerContext &ctx, ObjFile *f);
+TpiSource *makeTypeServerSource(COFFLinkerContext &ctx,
+ PDBInputFile *pdbInputFile);
+TpiSource *makeUseTypeServerSource(COFFLinkerContext &ctx, ObjFile *file,
llvm::codeview::TypeServer2Record ts);
-TpiSource *makePrecompSource(ObjFile *file);
-TpiSource *makeUsePrecompSource(ObjFile *file,
+TpiSource *makePrecompSource(COFFLinkerContext &ctx, ObjFile *file);
+TpiSource *makeUsePrecompSource(COFFLinkerContext &ctx, ObjFile *file,
llvm::codeview::PrecompRecord ts);
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
//===----------------------------------------------------------------------===//
#include "Driver.h"
+#include "COFFLinkerContext.h"
#include "Config.h"
#include "DebugTypes.h"
#include "ICF.h"
#include "Symbols.h"
#include "Writer.h"
#include "lld/Common/Args.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Driver.h"
-#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Filesystem.h"
-#include "lld/Common/Memory.h"
#include "lld/Common/Timer.h"
#include "lld/Common/Version.h"
-#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/StringSwitch.h"
+#include "llvm/ADT/Triple.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/TarWriter.h"
#include "llvm/Support/TargetSelect.h"
+#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/ToolDrivers/llvm-lib/LibDriver.h"
#include <algorithm>
#include <future>
#include <memory>
+#include <optional>
using namespace llvm;
using namespace llvm::object;
using namespace llvm::COFF;
using namespace llvm::sys;
-namespace lld {
-namespace coff {
+namespace lld::coff {
-static Timer inputFileTimer("Input File Reading", Timer::root());
+bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
+ // This driver-specific context will be freed later by lldMain().
+ auto *ctx = new COFFLinkerContext;
-Configuration *config;
-LinkerDriver *driver;
+ ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
+ ctx->e.logName = args::getFilenameWithoutExe(args[0]);
+ ctx->e.errorLimitExceededMsg = "too many errors emitted, stopping now"
+ " (use /errorlimit:0 to see all errors)";
-bool link(ArrayRef<const char *> args, bool canExitEarly, raw_ostream &stdoutOS,
- raw_ostream &stderrOS) {
- lld::stdoutOS = &stdoutOS;
- lld::stderrOS = &stderrOS;
+ ctx->driver.linkerMain(args);
- errorHandler().cleanupCallback = []() {
- TpiSource::clear();
- freeArena();
- ObjFile::instances.clear();
- PDBInputFile::instances.clear();
- ImportFile::instances.clear();
- BitcodeFile::instances.clear();
- memset(MergeChunk::instances, 0, sizeof(MergeChunk::instances));
- OutputSection::clear();
- };
-
- errorHandler().logName = args::getFilenameWithoutExe(args[0]);
- errorHandler().errorLimitExceededMsg =
- "too many errors emitted, stopping now"
- " (use /errorlimit:0 to see all errors)";
- errorHandler().exitEarly = canExitEarly;
- stderrOS.enable_colors(stderrOS.has_colors());
-
- config = make<Configuration>();
- symtab = make<SymbolTable>();
- driver = make<LinkerDriver>();
-
- driver->linkerMain(args);
-
- // Call exit() if we can to avoid calling destructors.
- if (canExitEarly)
- exitLld(errorCount() ? 1 : 0);
-
- bool ret = errorCount() == 0;
- if (!canExitEarly)
- errorHandler().reset();
- return ret;
+ return errorCount() == 0;
}
// Parse options of the form "old;new".
// Drop directory components and replace extension with
// ".exe", ".dll" or ".sys".
-static std::string getOutputPath(StringRef path) {
+static std::string getOutputPath(StringRef path, bool isDll, bool isDriver) {
StringRef ext = ".exe";
- if (config->dll)
+ if (isDll)
ext = ".dll";
- else if (config->driver)
+ else if (isDriver)
ext = ".sys";
return (sys::path::stem(path) + ext).str();
}
// Symbol names are mangled by prepending "_" on x86.
-static StringRef mangle(StringRef sym) {
- assert(config->machine != IMAGE_FILE_MACHINE_UNKNOWN);
- if (config->machine == I386)
- return saver.save("_" + sym);
+StringRef LinkerDriver::mangle(StringRef sym) {
+ assert(ctx.config.machine != IMAGE_FILE_MACHINE_UNKNOWN);
+ if (ctx.config.machine == I386)
+ return saver().save("_" + sym);
return sym;
}
-static bool findUnderscoreMangle(StringRef sym) {
- Symbol *s = symtab->findMangle(mangle(sym));
+llvm::Triple::ArchType LinkerDriver::getArch() {
+ switch (ctx.config.machine) {
+ case I386:
+ return llvm::Triple::ArchType::x86;
+ case AMD64:
+ return llvm::Triple::ArchType::x86_64;
+ case ARMNT:
+ return llvm::Triple::ArchType::arm;
+ case ARM64:
+ return llvm::Triple::ArchType::aarch64;
+ default:
+ return llvm::Triple::ArchType::UnknownArch;
+ }
+}
+
+bool LinkerDriver::findUnderscoreMangle(StringRef sym) {
+ Symbol *s = ctx.symtab.findMangle(mangle(sym));
return s && !isa<Undefined>(s);
}
MemoryBufferRef mbref = *mb;
make<std::unique_ptr<MemoryBuffer>>(std::move(mb)); // take ownership
- if (driver->tar)
- driver->tar->append(relativeToRoot(mbref.getBufferIdentifier()),
- mbref.getBuffer());
+ if (ctx.driver.tar)
+ ctx.driver.tar->append(relativeToRoot(mbref.getBufferIdentifier()),
+ mbref.getBuffer());
return mbref;
}
addArchiveBuffer(m, "<whole-archive>", filename, memberIndex++);
return;
}
- symtab->addFile(make<ArchiveFile>(mbref));
+ ctx.symtab.addFile(make<ArchiveFile>(ctx, mbref));
break;
case file_magic::bitcode:
- if (lazy)
- symtab->addFile(make<LazyObjFile>(mbref));
- else
- symtab->addFile(make<BitcodeFile>(mbref, "", 0));
+ ctx.symtab.addFile(make<BitcodeFile>(ctx, mbref, "", 0, lazy));
break;
case file_magic::coff_object:
case file_magic::coff_import_library:
- if (lazy)
- symtab->addFile(make<LazyObjFile>(mbref));
- else
- symtab->addFile(make<ObjFile>(mbref));
+ ctx.symtab.addFile(make<ObjFile>(ctx, mbref, lazy));
break;
case file_magic::pdb:
- symtab->addFile(make<PDBInputFile>(mbref));
+ ctx.symtab.addFile(make<PDBInputFile>(ctx, mbref));
break;
case file_magic::coff_cl_gl_object:
error(filename + ": is not a native COFF file. Recompile without /GL");
break;
case file_magic::pecoff_executable:
- if (config->mingw) {
- symtab->addFile(make<DLLFile>(mbref));
+ if (ctx.config.mingw) {
+ ctx.symtab.addFile(make<DLLFile>(ctx, mbref));
break;
}
if (filename.endswith_insensitive(".dll")) {
"import library?");
break;
}
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
default:
error(mbref.getBufferIdentifier() + ": unknown file type");
break;
// the option `/nodefaultlib` than a reference to a file in the root
// directory.
std::string nearest;
- if (optTable.findNearest(pathStr, nearest) > 1)
+ if (ctx.optTable.findNearest(pathStr, nearest) > 1)
error(msg);
else
error(msg + "; did you mean '" + nearest + "'");
} else
- driver->addBuffer(std::move(mbOrErr.first), wholeArchive, lazy);
+ ctx.driver.addBuffer(std::move(mbOrErr.first), wholeArchive, lazy);
});
}
uint64_t offsetInArchive) {
file_magic magic = identify_magic(mb.getBuffer());
if (magic == file_magic::coff_import_library) {
- InputFile *imp = make<ImportFile>(mb);
+ InputFile *imp = make<ImportFile>(ctx, mb);
imp->parentName = parentName;
- symtab->addFile(imp);
+ ctx.symtab.addFile(imp);
return;
}
InputFile *obj;
if (magic == file_magic::coff_object) {
- obj = make<ObjFile>(mb);
+ obj = make<ObjFile>(ctx, mb);
} else if (magic == file_magic::bitcode) {
- obj = make<BitcodeFile>(mb, parentName, offsetInArchive);
+ obj =
+ make<BitcodeFile>(ctx, mb, parentName, offsetInArchive, /*lazy=*/false);
+ } else if (magic == file_magic::coff_cl_gl_object) {
+ error(mb.getBufferIdentifier() +
+ ": is not a native COFF file. Recompile without /GL?");
+ return;
} else {
error("unknown file type: " + mb.getBufferIdentifier());
return;
}
obj->parentName = parentName;
- symtab->addFile(obj);
+ ctx.symtab.addFile(obj);
log("Loaded " + toString(obj) + " for " + symName);
}
auto reportBufferError = [=](Error &&e, StringRef childName) {
fatal("could not get the buffer for the member defining symbol " +
- toCOFFString(sym) + ": " + parentName + "(" + childName + "): " +
- toString(std::move(e)));
+ toCOFFString(ctx, sym) + ": " + parentName + "(" + childName +
+ "): " + toString(std::move(e)));
};
if (!c.getParent()->isThin()) {
reportBufferError(mbOrErr.takeError(), check(c.getFullName()));
MemoryBufferRef mb = mbOrErr.get();
enqueueTask([=]() {
- driver->addArchiveBuffer(mb, toCOFFString(sym), parentName,
- offsetInArchive);
+ ctx.driver.addArchiveBuffer(mb, toCOFFString(ctx, sym), parentName,
+ offsetInArchive);
});
return;
}
- std::string childName = CHECK(
- c.getFullName(),
- "could not get the filename for the member defining symbol " +
- toCOFFString(sym));
+ std::string childName =
+ CHECK(c.getFullName(),
+ "could not get the filename for the member defining symbol " +
+ toCOFFString(ctx, sym));
auto future = std::make_shared<std::future<MBErrPair>>(
createFutureForFile(childName));
enqueueTask([=]() {
reportBufferError(errorCodeToError(mbOrErr.second), childName);
// Pass empty string as archive name so that the original filename is
// used as the buffer identifier.
- driver->addArchiveBuffer(takeBuffer(std::move(mbOrErr.first)),
- toCOFFString(sym), "", /*OffsetInArchive=*/0);
+ ctx.driver.addArchiveBuffer(takeBuffer(std::move(mbOrErr.first)),
+ toCOFFString(ctx, sym), "",
+ /*OffsetInArchive=*/0);
});
}
-static bool isDecorated(StringRef sym) {
+bool LinkerDriver::isDecorated(StringRef sym) {
return sym.startswith("@") || sym.contains("@@") || sym.startswith("?") ||
- (!config->mingw && sym.contains('@'));
+ (!ctx.config.mingw && sym.contains('@'));
}
// Parses .drectve section contents and returns a list of files
log("Directives: " + toString(file) + ": " + s);
- ArgParser parser;
+ ArgParser parser(ctx);
// .drectve is always tokenized using Windows shell rules.
// /EXPORT: option can appear too many times, processing in fastpath.
ParsedDirectives directives = parser.parseDirectives(s);
continue;
Export exp = parseExport(e);
- if (config->machine == I386 && config->mingw) {
+ if (ctx.config.machine == I386 && ctx.config.mingw) {
if (!isDecorated(exp.name))
- exp.name = saver.save("_" + exp.name);
+ exp.name = saver().save("_" + exp.name);
if (!exp.extName.empty() && !isDecorated(exp.extName))
- exp.extName = saver.save("_" + exp.extName);
+ exp.extName = saver().save("_" + exp.extName);
}
exp.directives = true;
- config->exports.push_back(exp);
+ ctx.config.exports.push_back(exp);
}
// Handle /include: in bulk.
for (StringRef inc : directives.includes)
addUndefined(inc);
+ // Handle /exclude-symbols: in bulk.
+ for (StringRef e : directives.excludes) {
+ SmallVector<StringRef, 2> vec;
+ e.split(vec, ',');
+ for (StringRef sym : vec)
+ excludedSymbols.insert(mangle(sym));
+ }
+
+ // https://docs.microsoft.com/en-us/cpp/preprocessor/comment-c-cpp?view=msvc-160
for (auto *arg : directives.args) {
switch (arg->getOption().getID()) {
case OPT_aligncomm:
parseAlternateName(arg->getValue());
break;
case OPT_defaultlib:
- if (Optional<StringRef> path = findLib(arg->getValue()))
+ if (std::optional<StringRef> path = findLib(arg->getValue()))
enqueuePath(*path, false, false);
break;
case OPT_entry:
- config->entry = addUndefined(mangle(arg->getValue()));
+ ctx.config.entry = addUndefined(mangle(arg->getValue()));
break;
case OPT_failifmismatch:
checkFailIfMismatch(arg->getValue(), file);
case OPT_incl:
addUndefined(arg->getValue());
break;
+ case OPT_manifestdependency:
+ ctx.config.manifestDependencies.insert(arg->getValue());
+ break;
case OPT_merge:
parseMerge(arg->getValue());
break;
case OPT_nodefaultlib:
- config->noDefaultLibs.insert(doFindLib(arg->getValue()).lower());
+ ctx.config.noDefaultLibs.insert(doFindLib(arg->getValue()).lower());
+ break;
+ case OPT_release:
+ ctx.config.writeCheckSum = true;
break;
case OPT_section:
parseSection(arg->getValue());
break;
case OPT_stack:
- parseNumbers(arg->getValue(), &config->stackReserve,
- &config->stackCommit);
+ parseNumbers(arg->getValue(), &ctx.config.stackReserve,
+ &ctx.config.stackCommit);
break;
case OPT_subsystem: {
bool gotVersion = false;
- parseSubsystem(arg->getValue(), &config->subsystem,
- &config->majorSubsystemVersion,
- &config->minorSubsystemVersion, &gotVersion);
+ parseSubsystem(arg->getValue(), &ctx.config.subsystem,
+ &ctx.config.majorSubsystemVersion,
+ &ctx.config.minorSubsystemVersion, &gotVersion);
if (gotVersion) {
- config->majorOSVersion = config->majorSubsystemVersion;
- config->minorOSVersion = config->minorSubsystemVersion;
+ ctx.config.majorOSVersion = ctx.config.majorSubsystemVersion;
+ ctx.config.minorOSVersion = ctx.config.minorSubsystemVersion;
}
break;
}
case OPT_editandcontinue:
case OPT_guardsym:
case OPT_throwingnew:
+ case OPT_inferasanlibs:
+ case OPT_inferasanlibs_no:
break;
default:
- error(arg->getSpelling() + " is not allowed in .drectve");
+ error(arg->getSpelling() + " is not allowed in .drectve (" +
+ toString(file) + ")");
}
}
}
// Find file from search paths. You can omit ".obj", this function takes
// care of that. Note that the returned path is not guaranteed to exist.
StringRef LinkerDriver::doFindFile(StringRef filename) {
+ auto getFilename = [this](StringRef filename) -> StringRef {
+ if (ctx.config.vfs)
+ if (auto statOrErr = ctx.config.vfs->status(filename))
+ return saver().save(statOrErr->getName());
+ return filename;
+ };
+
bool hasPathSep = (filename.find_first_of("/\\") != StringRef::npos);
if (hasPathSep)
- return filename;
+ return getFilename(filename);
bool hasExt = filename.contains('.');
for (StringRef dir : searchPaths) {
SmallString<128> path = dir;
sys::path::append(path, filename);
+ path = SmallString<128>{getFilename(path.str())};
if (sys::fs::exists(path.str()))
- return saver.save(path.str());
+ return saver().save(path.str());
if (!hasExt) {
path.append(".obj");
+ path = SmallString<128>{getFilename(path.str())};
if (sys::fs::exists(path.str()))
- return saver.save(path.str());
+ return saver().save(path.str());
}
}
return filename;
}
-static Optional<sys::fs::UniqueID> getUniqueID(StringRef path) {
+static std::optional<sys::fs::UniqueID> getUniqueID(StringRef path) {
sys::fs::UniqueID ret;
if (sys::fs::getUniqueID(path, ret))
- return None;
+ return std::nullopt;
return ret;
}
// Resolves a file path. This never returns the same path
-// (in that case, it returns None).
-Optional<StringRef> LinkerDriver::findFile(StringRef filename) {
+// (in that case, it returns std::nullopt).
+std::optional<StringRef> LinkerDriver::findFile(StringRef filename) {
StringRef path = doFindFile(filename);
- if (Optional<sys::fs::UniqueID> id = getUniqueID(path)) {
+ if (std::optional<sys::fs::UniqueID> id = getUniqueID(path)) {
bool seen = !visitedFiles.insert(*id).second;
if (seen)
- return None;
+ return std::nullopt;
}
if (path.endswith_insensitive(".lib"))
- visitedLibs.insert(std::string(sys::path::filename(path)));
+ visitedLibs.insert(std::string(sys::path::filename(path).lower()));
return path;
}
SmallString<128> s = filename;
sys::path::replace_extension(s, ".a");
- StringRef libName = saver.save("lib" + s.str());
+ StringRef libName = saver().save("lib" + s.str());
return doFindFile(libName);
}
// Add ".lib" to Filename if that has no file extension.
bool hasExt = filename.contains('.');
if (!hasExt)
- filename = saver.save(filename + ".lib");
+ filename = saver().save(filename + ".lib");
StringRef ret = doFindFile(filename);
// For MinGW, if the find above didn't turn up anything, try
// looking for a MinGW formatted library name.
- if (config->mingw && ret == filename)
+ if (ctx.config.mingw && ret == filename)
return doFindLibMinGW(filename);
return ret;
}
// Resolves a library path. /nodefaultlib options are taken into
// consideration. This never returns the same path (in that case,
-// it returns None).
-Optional<StringRef> LinkerDriver::findLib(StringRef filename) {
- if (config->noDefaultLibAll)
- return None;
+// it returns std::nullopt).
+std::optional<StringRef> LinkerDriver::findLib(StringRef filename) {
+ if (ctx.config.noDefaultLibAll)
+ return std::nullopt;
if (!visitedLibs.insert(filename.lower()).second)
- return None;
+ return std::nullopt;
StringRef path = doFindLib(filename);
- if (config->noDefaultLibs.count(path.lower()))
- return None;
+ if (ctx.config.noDefaultLibs.count(path.lower()))
+ return std::nullopt;
- if (Optional<sys::fs::UniqueID> id = getUniqueID(path))
+ if (std::optional<sys::fs::UniqueID> id = getUniqueID(path))
if (!visitedFiles.insert(*id).second)
- return None;
+ return std::nullopt;
return path;
}
+void LinkerDriver::detectWinSysRoot(const opt::InputArgList &Args) {
+ IntrusiveRefCntPtr<vfs::FileSystem> VFS = vfs::getRealFileSystem();
+
+ // Check the command line first, that's the user explicitly telling us what to
+ // use. Check the environment next, in case we're being invoked from a VS
+ // command prompt. Failing that, just try to find the newest Visual Studio
+ // version we can and use its default VC toolchain.
+ std::optional<StringRef> VCToolsDir, VCToolsVersion, WinSysRoot;
+ if (auto *A = Args.getLastArg(OPT_vctoolsdir))
+ VCToolsDir = A->getValue();
+ if (auto *A = Args.getLastArg(OPT_vctoolsversion))
+ VCToolsVersion = A->getValue();
+ if (auto *A = Args.getLastArg(OPT_winsysroot))
+ WinSysRoot = A->getValue();
+ if (!findVCToolChainViaCommandLine(*VFS, VCToolsDir, VCToolsVersion,
+ WinSysRoot, vcToolChainPath, vsLayout) &&
+ (Args.hasArg(OPT_lldignoreenv) ||
+ !findVCToolChainViaEnvironment(*VFS, vcToolChainPath, vsLayout)) &&
+ !findVCToolChainViaSetupConfig(*VFS, vcToolChainPath, vsLayout) &&
+ !findVCToolChainViaRegistry(vcToolChainPath, vsLayout))
+ return;
+
+ // If the VC environment hasn't been configured (perhaps because the user did
+ // not run vcvarsall), try to build a consistent link environment. If the
+ // environment variable is set however, assume the user knows what they're
+ // doing. If the user passes /vctoolsdir or /winsdkdir, trust that over env
+ // vars.
+ if (const auto *A = Args.getLastArg(OPT_diasdkdir, OPT_winsysroot)) {
+ diaPath = A->getValue();
+ if (A->getOption().getID() == OPT_winsysroot)
+ path::append(diaPath, "DIA SDK");
+ }
+ useWinSysRootLibPath = Args.hasArg(OPT_lldignoreenv) ||
+ !Process::GetEnv("LIB") ||
+ Args.getLastArg(OPT_vctoolsdir, OPT_winsysroot);
+ if (Args.hasArg(OPT_lldignoreenv) || !Process::GetEnv("LIB") ||
+ Args.getLastArg(OPT_winsdkdir, OPT_winsysroot)) {
+ std::optional<StringRef> WinSdkDir, WinSdkVersion;
+ if (auto *A = Args.getLastArg(OPT_winsdkdir))
+ WinSdkDir = A->getValue();
+ if (auto *A = Args.getLastArg(OPT_winsdkversion))
+ WinSdkVersion = A->getValue();
+
+ if (useUniversalCRT(vsLayout, vcToolChainPath, getArch(), *VFS)) {
+ std::string UniversalCRTSdkPath;
+ std::string UCRTVersion;
+ if (getUniversalCRTSdkDir(*VFS, WinSdkDir, WinSdkVersion, WinSysRoot,
+ UniversalCRTSdkPath, UCRTVersion)) {
+ universalCRTLibPath = UniversalCRTSdkPath;
+ path::append(universalCRTLibPath, "Lib", UCRTVersion, "ucrt");
+ }
+ }
+
+ std::string sdkPath;
+ std::string windowsSDKIncludeVersion;
+ std::string windowsSDKLibVersion;
+ if (getWindowsSDKDir(*VFS, WinSdkDir, WinSdkVersion, WinSysRoot, sdkPath,
+ sdkMajor, windowsSDKIncludeVersion,
+ windowsSDKLibVersion)) {
+ windowsSdkLibPath = sdkPath;
+ path::append(windowsSdkLibPath, "Lib");
+ if (sdkMajor >= 8)
+ path::append(windowsSdkLibPath, windowsSDKLibVersion, "um");
+ }
+ }
+}
+
+void LinkerDriver::addWinSysRootLibSearchPaths() {
+ if (!diaPath.empty()) {
+ // The DIA SDK always uses the legacy vc arch, even in new MSVC versions.
+ path::append(diaPath, "lib", archToLegacyVCArch(getArch()));
+ searchPaths.push_back(saver().save(diaPath.str()));
+ }
+ if (useWinSysRootLibPath) {
+ searchPaths.push_back(saver().save(getSubDirectoryPath(
+ SubDirectoryType::Lib, vsLayout, vcToolChainPath, getArch())));
+ searchPaths.push_back(saver().save(
+ getSubDirectoryPath(SubDirectoryType::Lib, vsLayout, vcToolChainPath,
+ getArch(), "atlmfc")));
+ }
+ if (!universalCRTLibPath.empty()) {
+ StringRef ArchName = archToWindowsSDKArch(getArch());
+ if (!ArchName.empty()) {
+ path::append(universalCRTLibPath, ArchName);
+ searchPaths.push_back(saver().save(universalCRTLibPath.str()));
+ }
+ }
+ if (!windowsSdkLibPath.empty()) {
+ std::string path;
+ if (appendArchToWindowsSDKLibPath(sdkMajor, windowsSdkLibPath, getArch(),
+ path))
+ searchPaths.push_back(saver().save(path));
+ }
+}
+
// Parses LIB environment which contains a list of search paths.
void LinkerDriver::addLibSearchPaths() {
- Optional<std::string> envOpt = Process::GetEnv("LIB");
- if (!envOpt.hasValue())
+ std::optional<std::string> envOpt = Process::GetEnv("LIB");
+ if (!envOpt)
return;
- StringRef env = saver.save(*envOpt);
+ StringRef env = saver().save(*envOpt);
while (!env.empty()) {
StringRef path;
std::tie(path, env) = env.split(';');
}
Symbol *LinkerDriver::addUndefined(StringRef name) {
- Symbol *b = symtab->addUndefined(name);
+ Symbol *b = ctx.symtab.addUndefined(name);
if (!b->isGCRoot) {
b->isGCRoot = true;
- config->gcroot.push_back(b);
+ ctx.config.gcroot.push_back(b);
}
return b;
}
return "";
// Otherwise, see if a similar, mangled symbol exists in the symbol table.
- Symbol *mangled = symtab->findMangle(unmangled->getName());
+ Symbol *mangled = ctx.symtab.findMangle(unmangled->getName());
if (!mangled)
return "";
// If we find a similar mangled symbol, make this an alias to it and return
// its name.
log(unmangled->getName() + " aliased to " + mangled->getName());
- unmangled->weakAlias = symtab->addUndefined(mangled->getName());
+ unmangled->weakAlias = ctx.symtab.addUndefined(mangled->getName());
return mangled->getName();
}
// each of which corresponds to a user-defined "main" function. This function
// infers an entry point from a user-defined "main" function.
StringRef LinkerDriver::findDefaultEntry() {
- assert(config->subsystem != IMAGE_SUBSYSTEM_UNKNOWN &&
+ assert(ctx.config.subsystem != IMAGE_SUBSYSTEM_UNKNOWN &&
"must handle /subsystem before calling this");
- if (config->mingw)
- return mangle(config->subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI
+ if (ctx.config.mingw)
+ return mangle(ctx.config.subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI
? "WinMainCRTStartup"
: "mainCRTStartup");
- if (config->subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) {
+ if (ctx.config.subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI) {
if (findUnderscoreMangle("wWinMain")) {
if (!findUnderscoreMangle("WinMain"))
return mangle("wWinMainCRTStartup");
}
WindowsSubsystem LinkerDriver::inferSubsystem() {
- if (config->dll)
+ if (ctx.config.dll)
return IMAGE_SUBSYSTEM_WINDOWS_GUI;
- if (config->mingw)
+ if (ctx.config.mingw)
return IMAGE_SUBSYSTEM_WINDOWS_CUI;
// Note that link.exe infers the subsystem from the presence of these
// functions even if /entry: or /nodefaultlib are passed which causes them
return IMAGE_SUBSYSTEM_UNKNOWN;
}
-static uint64_t getDefaultImageBase() {
- if (config->is64())
- return config->dll ? 0x180000000 : 0x140000000;
- return config->dll ? 0x10000000 : 0x400000;
+uint64_t LinkerDriver::getDefaultImageBase() {
+ if (ctx.config.is64())
+ return ctx.config.dll ? 0x180000000 : 0x140000000;
+ return ctx.config.dll ? 0x10000000 : 0x400000;
}
static std::string rewritePath(StringRef s) {
case OPT_INPUT:
case OPT_defaultlib:
case OPT_libpath:
- case OPT_manifest:
- case OPT_manifest_colon:
- case OPT_manifestdependency:
- case OPT_manifestfile:
- case OPT_manifestinput:
- case OPT_manifestuac:
+ case OPT_winsysroot:
break;
case OPT_call_graph_ordering_file:
case OPT_deffile:
+ case OPT_manifestinput:
case OPT_natvis:
os << arg->getSpelling() << quote(rewritePath(arg->getValue())) << '\n';
break;
break;
}
case OPT_implib:
+ case OPT_manifestfile:
case OPT_pdb:
case OPT_pdbstripped:
case OPT_out:
return debugTypes;
}
-static std::string getMapFile(const opt::InputArgList &args,
- opt::OptSpecifier os, opt::OptSpecifier osFile) {
+std::string LinkerDriver::getMapFile(const opt::InputArgList &args,
+ opt::OptSpecifier os,
+ opt::OptSpecifier osFile) {
auto *arg = args.getLastArg(os, osFile);
if (!arg)
return "";
return arg->getValue();
assert(arg->getOption().getID() == os.getID());
- StringRef outFile = config->outputFile;
+ StringRef outFile = ctx.config.outputFile;
return (outFile.substr(0, outFile.rfind('.')) + ".map").str();
}
-static std::string getImplibPath() {
- if (!config->implib.empty())
- return std::string(config->implib);
- SmallString<128> out = StringRef(config->outputFile);
+std::string LinkerDriver::getImplibPath() {
+ if (!ctx.config.implib.empty())
+ return std::string(ctx.config.implib);
+ SmallString<128> out = StringRef(ctx.config.outputFile);
sys::path::replace_extension(out, ".lib");
return std::string(out.str());
}
// LINK | {value} | {value}.{.dll/.exe} | {output name}
// LIB | {value} | {value}.dll | {output name}.dll
//
-static std::string getImportName(bool asLib) {
+std::string LinkerDriver::getImportName(bool asLib) {
SmallString<128> out;
- if (config->importName.empty()) {
- out.assign(sys::path::filename(config->outputFile));
+ if (ctx.config.importName.empty()) {
+ out.assign(sys::path::filename(ctx.config.outputFile));
if (asLib)
sys::path::replace_extension(out, ".dll");
} else {
- out.assign(config->importName);
+ out.assign(ctx.config.importName);
if (!sys::path::has_extension(out))
sys::path::replace_extension(out,
- (config->dll || asLib) ? ".dll" : ".exe");
+ (ctx.config.dll || asLib) ? ".dll" : ".exe");
}
return std::string(out.str());
}
-static void createImportLibrary(bool asLib) {
+void LinkerDriver::createImportLibrary(bool asLib) {
std::vector<COFFShortExport> exports;
- for (Export &e1 : config->exports) {
+ for (Export &e1 : ctx.config.exports) {
COFFShortExport e2;
e2.Name = std::string(e1.name);
e2.SymbolName = std::string(e1.symbolName);
e2.ExtName = std::string(e1.extName);
+ e2.AliasTarget = std::string(e1.aliasTarget);
e2.Ordinal = e1.ordinal;
e2.Noname = e1.noname;
e2.Data = e1.data;
exports.push_back(e2);
}
- auto handleError = [](Error &&e) {
- handleAllErrors(std::move(e),
- [](ErrorInfoBase &eib) { error(eib.message()); });
- };
std::string libName = getImportName(asLib);
std::string path = getImplibPath();
- if (!config->incremental) {
- handleError(writeImportLibrary(libName, path, exports, config->machine,
- config->mingw));
+ if (!ctx.config.incremental) {
+ checkError(writeImportLibrary(libName, path, exports, ctx.config.machine,
+ ctx.config.mingw));
return;
}
ErrorOr<std::unique_ptr<MemoryBuffer>> oldBuf = MemoryBuffer::getFile(
path, /*IsText=*/false, /*RequiresNullTerminator=*/false);
if (!oldBuf) {
- handleError(writeImportLibrary(libName, path, exports, config->machine,
- config->mingw));
+ checkError(writeImportLibrary(libName, path, exports, ctx.config.machine,
+ ctx.config.mingw));
return;
}
fatal("cannot create temporary file for import library " + path + ": " +
ec.message());
- if (Error e = writeImportLibrary(libName, tmpName, exports, config->machine,
- config->mingw)) {
- handleError(std::move(e));
+ if (Error e = writeImportLibrary(libName, tmpName, exports,
+ ctx.config.machine, ctx.config.mingw)) {
+ checkError(std::move(e));
return;
}
tmpName, /*IsText=*/false, /*RequiresNullTerminator=*/false));
if ((*oldBuf)->getBuffer() != newBuf->getBuffer()) {
oldBuf->reset();
- handleError(errorCodeToError(sys::fs::rename(tmpName, path)));
+ checkError(errorCodeToError(sys::fs::rename(tmpName, path)));
} else {
sys::fs::remove(tmpName);
}
}
-static void parseModuleDefs(StringRef path) {
+void LinkerDriver::parseModuleDefs(StringRef path) {
std::unique_ptr<MemoryBuffer> mb =
CHECK(MemoryBuffer::getFile(path, /*IsText=*/false,
/*RequiresNullTerminator=*/false,
/*IsVolatile=*/true),
"could not open " + path);
COFFModuleDefinition m = check(parseCOFFModuleDefinition(
- mb->getMemBufferRef(), config->machine, config->mingw));
+ mb->getMemBufferRef(), ctx.config.machine, ctx.config.mingw));
// Include in /reproduce: output if applicable.
- driver->takeBuffer(std::move(mb));
+ ctx.driver.takeBuffer(std::move(mb));
- if (config->outputFile.empty())
- config->outputFile = std::string(saver.save(m.OutputFile));
- config->importName = std::string(saver.save(m.ImportName));
+ if (ctx.config.outputFile.empty())
+ ctx.config.outputFile = std::string(saver().save(m.OutputFile));
+ ctx.config.importName = std::string(saver().save(m.ImportName));
if (m.ImageBase)
- config->imageBase = m.ImageBase;
+ ctx.config.imageBase = m.ImageBase;
if (m.StackReserve)
- config->stackReserve = m.StackReserve;
+ ctx.config.stackReserve = m.StackReserve;
if (m.StackCommit)
- config->stackCommit = m.StackCommit;
+ ctx.config.stackCommit = m.StackCommit;
if (m.HeapReserve)
- config->heapReserve = m.HeapReserve;
+ ctx.config.heapReserve = m.HeapReserve;
if (m.HeapCommit)
- config->heapCommit = m.HeapCommit;
+ ctx.config.heapCommit = m.HeapCommit;
if (m.MajorImageVersion)
- config->majorImageVersion = m.MajorImageVersion;
+ ctx.config.majorImageVersion = m.MajorImageVersion;
if (m.MinorImageVersion)
- config->minorImageVersion = m.MinorImageVersion;
+ ctx.config.minorImageVersion = m.MinorImageVersion;
if (m.MajorOSVersion)
- config->majorOSVersion = m.MajorOSVersion;
+ ctx.config.majorOSVersion = m.MajorOSVersion;
if (m.MinorOSVersion)
- config->minorOSVersion = m.MinorOSVersion;
+ ctx.config.minorOSVersion = m.MinorOSVersion;
for (COFFShortExport e1 : m.Exports) {
Export e2;
// DLL instead. This is supported by both MS and GNU linkers.
if (!e1.ExtName.empty() && e1.ExtName != e1.Name &&
StringRef(e1.Name).contains('.')) {
- e2.name = saver.save(e1.ExtName);
- e2.forwardTo = saver.save(e1.Name);
- config->exports.push_back(e2);
+ e2.name = saver().save(e1.ExtName);
+ e2.forwardTo = saver().save(e1.Name);
+ ctx.config.exports.push_back(e2);
continue;
}
- e2.name = saver.save(e1.Name);
- e2.extName = saver.save(e1.ExtName);
+ e2.name = saver().save(e1.Name);
+ e2.extName = saver().save(e1.ExtName);
+ e2.aliasTarget = saver().save(e1.AliasTarget);
e2.ordinal = e1.Ordinal;
e2.noname = e1.Noname;
e2.data = e1.Data;
e2.isPrivate = e1.Private;
e2.constant = e1.Constant;
- config->exports.push_back(e2);
+ ctx.config.exports.push_back(e2);
}
}
}
bool LinkerDriver::run() {
- ScopedTimer t(inputFileTimer);
+ ScopedTimer t(ctx.inputFileTimer);
bool didWork = !taskQueue.empty();
while (!taskQueue.empty()) {
// Parse an /order file. If an option is given, the linker places
// COMDAT sections in the same order as their names appear in the
// given file.
-static void parseOrderFile(StringRef arg) {
+void LinkerDriver::parseOrderFile(StringRef arg) {
// For some reason, the MSVC linker requires a filename to be
// preceded by "@".
if (!arg.startswith("@")) {
// Get a list of all comdat sections for error checking.
DenseSet<StringRef> set;
- for (Chunk *c : symtab->getChunks())
+ for (Chunk *c : ctx.symtab.getChunks())
if (auto *sec = dyn_cast<SectionChunk>(c))
if (sec->sym)
set.insert(sec->sym->getName());
// end of an output section.
for (StringRef arg : args::getLines(mb->getMemBufferRef())) {
std::string s(arg);
- if (config->machine == I386 && !isDecorated(s))
+ if (ctx.config.machine == I386 && !isDecorated(s))
s = "_" + s;
if (set.count(s) == 0) {
- if (config->warnMissingOrderSymbol)
+ if (ctx.config.warnMissingOrderSymbol)
warn("/order:" + arg + ": missing symbol: " + s + " [LNK4037]");
}
else
- config->order[s] = INT_MIN + config->order.size();
+ ctx.config.order[s] = INT_MIN + ctx.config.order.size();
}
// Include in /reproduce: output if applicable.
- driver->takeBuffer(std::move(mb));
+ ctx.driver.takeBuffer(std::move(mb));
}
-static void parseCallGraphFile(StringRef path) {
+void LinkerDriver::parseCallGraphFile(StringRef path) {
std::unique_ptr<MemoryBuffer> mb =
CHECK(MemoryBuffer::getFile(path, /*IsText=*/false,
/*RequiresNullTerminator=*/false,
// Build a map from symbol name to section.
DenseMap<StringRef, Symbol *> map;
- for (ObjFile *file : ObjFile::instances)
+ for (ObjFile *file : ctx.objFileInstances)
for (Symbol *sym : file->getSymbols())
if (sym)
map[sym->getName()] = sym;
auto findSection = [&](StringRef name) -> SectionChunk * {
Symbol *sym = map.lookup(name);
if (!sym) {
- if (config->warnMissingOrderSymbol)
+ if (ctx.config.warnMissingOrderSymbol)
warn(path + ": no such symbol: " + name);
return nullptr;
}
if (SectionChunk *from = findSection(fields[0]))
if (SectionChunk *to = findSection(fields[1]))
- config->callGraphProfile[{from, to}] += count;
+ ctx.config.callGraphProfile[{from, to}] += count;
}
// Include in /reproduce: output if applicable.
- driver->takeBuffer(std::move(mb));
+ ctx.driver.takeBuffer(std::move(mb));
}
-static void readCallGraphsFromObjectFiles() {
- for (ObjFile *obj : ObjFile::instances) {
+static void readCallGraphsFromObjectFiles(COFFLinkerContext &ctx) {
+ for (ObjFile *obj : ctx.objFileInstances) {
if (obj->callgraphSec) {
ArrayRef<uint8_t> contents;
cantFail(
auto *from = dyn_cast_or_null<SectionChunk>(fromSym->getChunk());
auto *to = dyn_cast_or_null<SectionChunk>(toSym->getChunk());
if (from && to)
- config->callGraphProfile[{from, to}] += count;
+ ctx.config.callGraphProfile[{from, to}] += count;
}
}
}
c->keepUnique = true;
}
-static void findKeepUniqueSections() {
+static void findKeepUniqueSections(COFFLinkerContext &ctx) {
// Exported symbols could be address-significant in other executables or DSOs,
// so we conservatively mark them as address-significant.
- for (Export &r : config->exports)
+ for (Export &r : ctx.config.exports)
markAddrsig(r.sym);
// Visit the address-significance table in each object file and mark each
// referenced symbol as address-significant.
- for (ObjFile *obj : ObjFile::instances) {
+ for (ObjFile *obj : ctx.objFileInstances) {
ArrayRef<Symbol *> syms = obj->getSymbols();
if (obj->addrsigSec) {
ArrayRef<uint8_t> contents;
// binary).
// lld only supports %_PDB% and %_EXT% and warns on references to all other env
// vars.
-static void parsePDBAltPath(StringRef altPath) {
+void LinkerDriver::parsePDBAltPath() {
SmallString<128> buf;
StringRef pdbBasename =
- sys::path::filename(config->pdbPath, sys::path::Style::windows);
+ sys::path::filename(ctx.config.pdbPath, sys::path::Style::windows);
StringRef binaryExtension =
- sys::path::extension(config->outputFile, sys::path::Style::windows);
+ sys::path::extension(ctx.config.outputFile, sys::path::Style::windows);
if (!binaryExtension.empty())
binaryExtension = binaryExtension.substr(1); // %_EXT% does not include '.'.
// v v v
// a...%...%...
size_t cursor = 0;
- while (cursor < altPath.size()) {
+ while (cursor < ctx.config.pdbAltPath.size()) {
size_t firstMark, secondMark;
- if ((firstMark = altPath.find('%', cursor)) == StringRef::npos ||
- (secondMark = altPath.find('%', firstMark + 1)) == StringRef::npos) {
+ if ((firstMark = ctx.config.pdbAltPath.find('%', cursor)) ==
+ StringRef::npos ||
+ (secondMark = ctx.config.pdbAltPath.find('%', firstMark + 1)) ==
+ StringRef::npos) {
// Didn't find another full fragment, treat rest of string as literal.
- buf.append(altPath.substr(cursor));
+ buf.append(ctx.config.pdbAltPath.substr(cursor));
break;
}
// Found a full fragment. Append text in front of first %, and interpret
// text between first and second % as variable name.
- buf.append(altPath.substr(cursor, firstMark - cursor));
- StringRef var = altPath.substr(firstMark, secondMark - firstMark + 1);
+ buf.append(ctx.config.pdbAltPath.substr(cursor, firstMark - cursor));
+ StringRef var =
+ ctx.config.pdbAltPath.substr(firstMark, secondMark - firstMark + 1);
if (var.equals_insensitive("%_pdb%"))
buf.append(pdbBasename);
else if (var.equals_insensitive("%_ext%"))
cursor = secondMark + 1;
}
- config->pdbAltPath = buf;
+ ctx.config.pdbAltPath = buf;
}
/// Convert resource files and potentially merge input resource object
void LinkerDriver::convertResources() {
std::vector<ObjFile *> resourceObjFiles;
- for (ObjFile *f : ObjFile::instances) {
+ for (ObjFile *f : ctx.objFileInstances) {
if (f->isResourceObjFile())
resourceObjFiles.push_back(f);
}
- if (!config->mingw &&
+ if (!ctx.config.mingw &&
(resourceObjFiles.size() > 1 ||
(resourceObjFiles.size() == 1 && !resources.empty()))) {
error((!resources.empty() ? "internal .obj file created from .res files"
f->includeResourceChunks();
return;
}
- ObjFile *f = make<ObjFile>(convertResToCOFF(resources, resourceObjFiles));
- symtab->addFile(f);
+ ObjFile *f =
+ make<ObjFile>(ctx, convertResToCOFF(resources, resourceObjFiles));
+ ctx.symtab.addFile(f);
f->includeResourceChunks();
}
// than MinGW in the case that nothing is explicitly exported.
void LinkerDriver::maybeExportMinGWSymbols(const opt::InputArgList &args) {
if (!args.hasArg(OPT_export_all_symbols)) {
- if (!config->dll)
+ if (!ctx.config.dll)
return;
- if (!config->exports.empty())
+ if (!ctx.config.exports.empty())
return;
if (args.hasArg(OPT_exclude_all_symbols))
return;
}
- AutoExporter exporter;
+ AutoExporter exporter(ctx, excludedSymbols);
for (auto *arg : args.filtered(OPT_wholearchive_file))
- if (Optional<StringRef> path = doFindFile(arg->getValue()))
+ if (std::optional<StringRef> path = doFindFile(arg->getValue()))
exporter.addWholeArchive(*path);
- symtab->forEachSymbol([&](Symbol *s) {
+ for (auto *arg : args.filtered(OPT_exclude_symbols)) {
+ SmallVector<StringRef, 2> vec;
+ StringRef(arg->getValue()).split(vec, ',');
+ for (StringRef sym : vec)
+ exporter.addExcludedSymbol(mangle(sym));
+ }
+
+ ctx.symtab.forEachSymbol([&](Symbol *s) {
auto *def = dyn_cast<Defined>(s);
if (!exporter.shouldExport(def))
return;
if (!def->isGCRoot) {
def->isGCRoot = true;
- config->gcroot.push_back(def);
+ ctx.config.gcroot.push_back(def);
}
Export e;
if (!(c->getOutputCharacteristics() & IMAGE_SCN_MEM_EXECUTE))
e.data = true;
s->isUsedInRegularObj = true;
- config->exports.push_back(e);
+ ctx.config.exports.push_back(e);
});
}
// /linkrepro and /reproduce are very similar, but /linkrepro takes a directory
// name while /reproduce takes a full path. We have /linkrepro for compatibility
// with Microsoft link.exe.
-Optional<std::string> getReproduceFile(const opt::InputArgList &args) {
+std::optional<std::string> getReproduceFile(const opt::InputArgList &args) {
if (auto *arg = args.getLastArg(OPT_reproduce))
return std::string(arg->getValue());
if (auto *path = getenv("LLD_REPRODUCE"))
return std::string(path);
- return None;
+ return std::nullopt;
+}
+
+static std::unique_ptr<llvm::vfs::FileSystem>
+getVFS(const opt::InputArgList &args) {
+ using namespace llvm::vfs;
+
+ const opt::Arg *arg = args.getLastArg(OPT_vfsoverlay);
+ if (!arg)
+ return nullptr;
+
+ auto bufOrErr = llvm::MemoryBuffer::getFile(arg->getValue());
+ if (!bufOrErr) {
+ checkError(errorCodeToError(bufOrErr.getError()));
+ return nullptr;
+ }
+
+ if (auto ret = vfs::getVFSFromYAML(std::move(*bufOrErr), /*DiagHandler*/ nullptr,
+ arg->getValue()))
+ return ret;
+
+ error("Invalid vfs overlay");
+ return nullptr;
}
void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
- ScopedTimer rootTimer(Timer::root());
+ ScopedTimer rootTimer(ctx.rootTimer);
+ Configuration *config = &ctx.config;
// Needed for LTO.
InitializeAllTargetInfos();
}
// Parse command line options.
- ArgParser parser;
+ ArgParser parser(ctx);
opt::InputArgList args = parser.parse(argsArr);
// Parse and evaluate -mllvm options.
std::vector<const char *> v;
v.push_back("lld-link (LLVM option parsing)");
- for (auto *arg : args.filtered(OPT_mllvm))
+ for (const auto *arg : args.filtered(OPT_mllvm)) {
v.push_back(arg->getValue());
+ config->mllvmOpts.emplace_back(arg->getValue());
+ }
cl::ResetAllOptionOccurrences();
cl::ParseCommandLineOptions(v.size(), v.data());
errorHandler().errorLimit = n;
}
+ config->vfs = getVFS(args);
+
// Handle /help
if (args.hasArg(OPT_help)) {
printHelp(argsArr[0]);
// Handle /lldmingw early, since it can potentially affect how other
// options are handled.
config->mingw = args.hasArg(OPT_lldmingw);
+ if (config->mingw)
+ ctx.e.errorLimitExceededMsg = "too many errors emitted, stopping now"
+ " (use --error-limit=0 to see all errors)";
// Handle /linkrepro and /reproduce.
- if (Optional<std::string> path = getReproduceFile(args)) {
+ if (std::optional<std::string> path = getReproduceFile(args)) {
Expected<std::unique_ptr<TarWriter>> errOrWriter =
TarWriter::create(*path, sys::path::stem(*path));
searchPaths.push_back("");
for (auto *arg : args.filtered(OPT_libpath))
searchPaths.push_back(arg->getValue());
- if (!args.hasArg(OPT_lldignoreenv))
+ detectWinSysRoot(args);
+ if (!args.hasArg(OPT_lldignoreenv) && !args.hasArg(OPT_winsysroot))
addLibSearchPaths();
// Handle /ignore
}
// Handle /demangle
- config->demangle = args.hasFlag(OPT_demangle, OPT_demangle_no);
+ config->demangle = args.hasFlag(OPT_demangle, OPT_demangle_no, true);
// Handle /debugtype
config->debugTypes = parseDebugTypes(args);
config->pdbPath = arg->getValue();
if (auto *arg = args.getLastArg(OPT_pdbaltpath))
config->pdbAltPath = arg->getValue();
+ if (auto *arg = args.getLastArg(OPT_pdbpagesize))
+ parsePDBPageSize(arg->getValue());
if (args.hasArg(OPT_natvis))
config->natvisFiles = args.getAllArgValues(OPT_natvis);
if (args.hasArg(OPT_pdbstream)) {
config->machine = getMachineType(arg->getValue());
if (config->machine == IMAGE_FILE_MACHINE_UNKNOWN)
fatal(Twine("unknown /machine argument: ") + arg->getValue());
+ addWinSysRootLibSearchPaths();
}
// Handle /nodefaultlib:<filename>
if (auto *arg = args.getLastArg(OPT_implib))
config->implib = arg->getValue();
+ config->noimplib = args.hasArg(OPT_noimplib);
+
// Handle /opt.
bool doGC = debug == DebugKind::None || args.hasArg(OPT_profile);
- Optional<ICFLevel> icfLevel = None;
+ std::optional<ICFLevel> icfLevel;
if (args.hasArg(OPT_profile))
icfLevel = ICFLevel::None;
unsigned tailMerge = 1;
- bool ltoNewPM = LLVM_ENABLE_NEW_PASS_MANAGER;
bool ltoDebugPM = false;
for (auto *arg : args.filtered(OPT_opt)) {
std::string str = StringRef(arg->getValue()).lower();
} else if (s == "nolldtailmerge") {
tailMerge = 0;
} else if (s == "ltonewpassmanager") {
- ltoNewPM = true;
- } else if (s == "noltonewpassmanager") {
- ltoNewPM = false;
+ /* We always use the new PM. */
} else if (s == "ltodebugpassmanager") {
ltoDebugPM = true;
} else if (s == "noltodebugpassmanager") {
if (!icfLevel)
icfLevel = doGC ? ICFLevel::All : ICFLevel::None;
config->doGC = doGC;
- config->doICF = icfLevel.getValue();
+ config->doICF = *icfLevel;
config->tailMerge =
(tailMerge == 1 && config->doICF != ICFLevel::None) || tailMerge == 2;
- config->ltoNewPassManager = ltoNewPM;
config->ltoDebugPassManager = ltoDebugPM;
// Handle /lldsavetemps
for (auto *arg : args.filtered(OPT_aligncomm))
parseAligncomm(arg->getValue());
- // Handle /manifestdependency. This enables /manifest unless /manifest:no is
- // also passed.
- if (auto *arg = args.getLastArg(OPT_manifestdependency)) {
- config->manifestDependency = arg->getValue();
- config->manifest = Configuration::SideBySide;
- }
+ // Handle /manifestdependency.
+ for (auto *arg : args.filtered(OPT_manifestdependency))
+ config->manifestDependencies.insert(arg->getValue());
// Handle /manifest and /manifest:
if (auto *arg = args.getLastArg(OPT_manifest, OPT_manifest_colon)) {
config->ltoCSProfileGenerate = args.hasArg(OPT_lto_cs_profile_generate);
config->ltoCSProfileFile = args.getLastArgValue(OPT_lto_cs_profile_file);
// Handle miscellaneous boolean flags.
+ config->ltoPGOWarnMismatch = args.hasFlag(OPT_lto_pgo_warn_mismatch,
+ OPT_lto_pgo_warn_mismatch_no, true);
config->allowBind = args.hasFlag(OPT_allowbind, OPT_allowbind_no, true);
config->allowIsolation =
args.hasFlag(OPT_allowisolation, OPT_allowisolation_no, true);
args.hasFlag(OPT_stdcall_fixup, OPT_stdcall_fixup_no, config->mingw);
config->warnStdcallFixup = !args.hasArg(OPT_stdcall_fixup);
+ if (args.hasFlag(OPT_inferasanlibs, OPT_inferasanlibs_no, false))
+ warn("ignoring '/inferasanlibs', this flag is not supported");
+
// Don't warn about long section names, such as .debug_info, for mingw or
// when -debug:dwarf is requested.
if (config->mingw || config->debugDwarf)
config->warnLongSectionNames = false;
- config->lldmapFile = getMapFile(args, OPT_lldmap, OPT_lldmap_file);
- config->mapFile = getMapFile(args, OPT_map, OPT_map_file);
-
- if (config->lldmapFile != "" && config->lldmapFile == config->mapFile) {
- warn("/lldmap and /map have the same output file '" + config->mapFile +
- "'.\n>>> ignoring /lldmap");
- config->lldmapFile.clear();
- }
-
if (config->incremental && args.hasArg(OPT_profile)) {
warn("ignoring '/incremental' due to '/profile' specification");
config->incremental = false;
std::set<sys::fs::UniqueID> wholeArchives;
for (auto *arg : args.filtered(OPT_wholearchive_file))
- if (Optional<StringRef> path = doFindFile(arg->getValue()))
- if (Optional<sys::fs::UniqueID> id = getUniqueID(*path))
+ if (std::optional<StringRef> path = doFindFile(arg->getValue()))
+ if (std::optional<sys::fs::UniqueID> id = getUniqueID(*path))
wholeArchives.insert(*id);
// A predicate returning true if a given path is an argument for
auto isWholeArchive = [&](StringRef path) -> bool {
if (args.hasArg(OPT_wholearchive_flag))
return true;
- if (Optional<sys::fs::UniqueID> id = getUniqueID(path))
+ if (std::optional<sys::fs::UniqueID> id = getUniqueID(path))
return wholeArchives.count(*id);
return false;
};
inLib = true;
break;
case OPT_wholearchive_file:
- if (Optional<StringRef> path = findFile(arg->getValue()))
+ if (std::optional<StringRef> path = findFile(arg->getValue()))
enqueuePath(*path, true, inLib);
break;
case OPT_INPUT:
- if (Optional<StringRef> path = findFile(arg->getValue()))
+ if (std::optional<StringRef> path = findFile(arg->getValue()))
enqueuePath(*path, isWholeArchive(*path), inLib);
break;
default:
}
}
- // Process files specified as /defaultlib. These should be enequeued after
- // other files, which is why they are in a separate loop.
- for (auto *arg : args.filtered(OPT_defaultlib))
- if (Optional<StringRef> path = findLib(arg->getValue()))
- enqueuePath(*path, false, false);
-
- // Windows specific -- Create a resource file containing a manifest file.
- if (config->manifest == Configuration::Embed)
- addBuffer(createManifestRes(), false, false);
-
// Read all input files given via the command line.
run();
-
if (errorCount())
return;
if (config->machine == IMAGE_FILE_MACHINE_UNKNOWN) {
warn("/machine is not specified. x64 is assumed");
config->machine = AMD64;
+ addWinSysRootLibSearchPaths();
}
config->wordsize = config->is64() ? 8 : 4;
+ // Process files specified as /defaultlib. These must be processed after
+ // addWinSysRootLibSearchPaths(), which is why they are in a separate loop.
+ for (auto *arg : args.filtered(OPT_defaultlib))
+ if (std::optional<StringRef> path = findLib(arg->getValue()))
+ enqueuePath(*path, false, false);
+ run();
+ if (errorCount())
+ return;
+
+ // Handle /RELEASE
+ if (args.hasArg(OPT_release))
+ config->writeCheckSum = true;
+
// Handle /safeseh, x86 only, on by default, except for mingw.
if (config->machine == I386) {
config->safeSEH = args.hasFlag(OPT_safeseh, OPT_safeseh_no, !config->mingw);
// Handle /functionpadmin
for (auto *arg : args.filtered(OPT_functionpadmin, OPT_functionpadmin_opt))
- parseFunctionPadMin(arg, config->machine);
+ parseFunctionPadMin(arg);
- if (tar)
+ if (tar) {
tar->append("response.txt",
createResponseFile(args, filePaths,
ArrayRef<StringRef>(searchPaths).slice(1)));
+ }
// Handle /largeaddressaware
config->largeAddressAware = args.hasFlag(
Export e = parseExport(arg->getValue());
if (config->machine == I386) {
if (!isDecorated(e.name))
- e.name = saver.save("_" + e.name);
+ e.name = saver().save("_" + e.name);
if (!e.extName.empty() && !isDecorated(e.extName))
- e.extName = saver.save("_" + e.extName);
+ e.extName = saver().save("_" + e.extName);
}
config->exports.push_back(e);
}
// Handle generation of import library from a def file.
if (!args.hasArg(OPT_INPUT, OPT_wholearchive_file)) {
fixupExports();
- createImportLibrary(/*asLib=*/true);
+ if (!config->noimplib)
+ createImportLibrary(/*asLib=*/true);
return;
}
// Set default image name if neither /out or /def set it.
if (config->outputFile.empty()) {
config->outputFile = getOutputPath(
- (*args.filtered(OPT_INPUT, OPT_wholearchive_file).begin())->getValue());
+ (*args.filtered(OPT_INPUT, OPT_wholearchive_file).begin())->getValue(),
+ config->dll, config->driver);
}
// Fail early if an output file is not writable.
return;
}
+ config->lldmapFile = getMapFile(args, OPT_lldmap, OPT_lldmap_file);
+ config->mapFile = getMapFile(args, OPT_map, OPT_map_file);
+
+ if (config->mapFile != "" && args.hasArg(OPT_map_info)) {
+ for (auto *arg : args.filtered(OPT_map_info)) {
+ std::string s = StringRef(arg->getValue()).lower();
+ if (s == "exports")
+ config->mapInfo = true;
+ else
+ error("unknown option: /mapinfo:" + s);
+ }
+ }
+
+ if (config->lldmapFile != "" && config->lldmapFile == config->mapFile) {
+ warn("/lldmap and /map have the same output file '" + config->mapFile +
+ "'.\n>>> ignoring /lldmap");
+ config->lldmapFile.clear();
+ }
+
if (shouldCreatePDB) {
// Put the PDB next to the image if no /pdb flag was passed.
if (config->pdbPath.empty()) {
sys::fs::make_absolute(config->pdbAltPath);
sys::path::remove_dots(config->pdbAltPath);
} else {
- // Don't do this earlier, so that Config->OutputFile is ready.
- parsePDBAltPath(config->pdbAltPath);
+ // Don't do this earlier, so that ctx.OutputFile is ready.
+ parsePDBAltPath();
}
}
if (config->imageBase == uint64_t(-1))
config->imageBase = getDefaultImageBase();
- symtab->addSynthetic(mangle("__ImageBase"), nullptr);
+ ctx.symtab.addSynthetic(mangle("__ImageBase"), nullptr);
if (config->machine == I386) {
- symtab->addAbsolute("___safe_se_handler_table", 0);
- symtab->addAbsolute("___safe_se_handler_count", 0);
+ ctx.symtab.addAbsolute("___safe_se_handler_table", 0);
+ ctx.symtab.addAbsolute("___safe_se_handler_count", 0);
}
- symtab->addAbsolute(mangle("__guard_fids_count"), 0);
- symtab->addAbsolute(mangle("__guard_fids_table"), 0);
- symtab->addAbsolute(mangle("__guard_flags"), 0);
- symtab->addAbsolute(mangle("__guard_iat_count"), 0);
- symtab->addAbsolute(mangle("__guard_iat_table"), 0);
- symtab->addAbsolute(mangle("__guard_longjmp_count"), 0);
- symtab->addAbsolute(mangle("__guard_longjmp_table"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_fids_count"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_fids_table"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_flags"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_iat_count"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_iat_table"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_longjmp_count"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_longjmp_table"), 0);
// Needed for MSVC 2017 15.5 CRT.
- symtab->addAbsolute(mangle("__enclave_config"), 0);
+ ctx.symtab.addAbsolute(mangle("__enclave_config"), 0);
// Needed for MSVC 2019 16.8 CRT.
- symtab->addAbsolute(mangle("__guard_eh_cont_count"), 0);
- symtab->addAbsolute(mangle("__guard_eh_cont_table"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_eh_cont_count"), 0);
+ ctx.symtab.addAbsolute(mangle("__guard_eh_cont_table"), 0);
if (config->pseudoRelocs) {
- symtab->addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST__"), 0);
- symtab->addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST_END__"), 0);
+ ctx.symtab.addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST__"), 0);
+ ctx.symtab.addAbsolute(mangle("__RUNTIME_PSEUDO_RELOC_LIST_END__"), 0);
}
if (config->mingw) {
- symtab->addAbsolute(mangle("__CTOR_LIST__"), 0);
- symtab->addAbsolute(mangle("__DTOR_LIST__"), 0);
+ ctx.symtab.addAbsolute(mangle("__CTOR_LIST__"), 0);
+ ctx.symtab.addAbsolute(mangle("__DTOR_LIST__"), 0);
}
// This code may add new undefined symbols to the link, which may enqueue more
for (auto pair : config->alternateNames) {
StringRef from = pair.first;
StringRef to = pair.second;
- Symbol *sym = symtab->find(from);
+ Symbol *sym = ctx.symtab.find(from);
if (!sym)
continue;
if (auto *u = dyn_cast<Undefined>(sym))
if (!u->weakAlias)
- u->weakAlias = symtab->addUndefined(to);
+ u->weakAlias = ctx.symtab.addUndefined(to);
}
// If any inputs are bitcode files, the LTO code generator may create
// file's symbol table. If any of those library functions are defined in a
// bitcode file in an archive member, we need to arrange to use LTO to
// compile those archive members by adding them to the link beforehand.
- if (!BitcodeFile::instances.empty())
+ if (!ctx.bitcodeFileInstances.empty())
for (auto *s : lto::LTO::getRuntimeLibcallSymbols())
- symtab->addLibcall(s);
+ ctx.symtab.addLibcall(s);
// Windows specific -- if __load_config_used can be resolved, resolve it.
- if (symtab->findUnderscore("_load_config_used"))
+ if (ctx.symtab.findUnderscore("_load_config_used"))
addUndefined(mangle("_load_config_used"));
- } while (run());
- if (args.hasArg(OPT_include_optional)) {
- // Handle /includeoptional
- for (auto *arg : args.filtered(OPT_include_optional))
- if (dyn_cast_or_null<LazyArchive>(symtab->find(arg->getValue())))
- addUndefined(arg->getValue());
- while (run());
- }
+ if (args.hasArg(OPT_include_optional)) {
+ // Handle /includeoptional
+ for (auto *arg : args.filtered(OPT_include_optional))
+ if (isa_and_nonnull<LazyArchive>(ctx.symtab.find(arg->getValue())))
+ addUndefined(arg->getValue());
+ }
+ } while (run());
// Create wrapped symbols for -wrap option.
- std::vector<WrappedSymbol> wrapped = addWrappedSymbols(args);
+ std::vector<WrappedSymbol> wrapped = addWrappedSymbols(ctx, args);
// Load more object files that might be needed for wrapped symbols.
if (!wrapped.empty())
while (run());
// If it ends up pulling in more object files from static libraries,
// (and maybe doing more stdcall fixups along the way), this would need
// to loop these two calls.
- symtab->loadMinGWSymbols();
+ ctx.symtab.loadMinGWSymbols();
run();
}
// If we are going to do codegen for link-time optimization, check for
// unresolvable symbols first, so we don't spend time generating code that
// will fail to link anyway.
- if (!BitcodeFile::instances.empty() && !config->forceUnresolved)
- symtab->reportUnresolvable();
+ if (!ctx.bitcodeFileInstances.empty() && !config->forceUnresolved)
+ ctx.symtab.reportUnresolvable();
if (errorCount())
return;
// Do LTO by compiling bitcode input files to a set of native COFF files then
// link those files (unless -thinlto-index-only was given, in which case we
// resolve symbols and write indices, but don't generate native code or link).
- symtab->addCombinedLTOObjects();
+ ctx.symtab.compileBitcodeFiles();
// If -thinlto-index-only is given, we should create only "index
// files" and not object files. Index file creation is already done
// Apply symbol renames for -wrap.
if (!wrapped.empty())
- wrapSymbols(wrapped);
+ wrapSymbols(ctx, wrapped);
// Resolve remaining undefined symbols and warn about imported locals.
- symtab->resolveRemainingUndefines();
+ ctx.symtab.resolveRemainingUndefines();
if (errorCount())
return;
// order provided on the command line, while lld will pull in needed
// files from static libraries only after the last object file on the
// command line.
- for (auto i = ObjFile::instances.begin(), e = ObjFile::instances.end();
+ for (auto i = ctx.objFileInstances.begin(), e = ctx.objFileInstances.end();
i != e; i++) {
ObjFile *file = *i;
if (isCrtend(file->getName())) {
- ObjFile::instances.erase(i);
- ObjFile::instances.push_back(file);
+ ctx.objFileInstances.erase(i);
+ ctx.objFileInstances.push_back(file);
break;
}
}
// -implib option is given explicitly, for compatibility with GNU ld.
if (!config->exports.empty() || config->dll) {
fixupExports();
- if (!config->mingw || !config->implib.empty())
+ if (!config->noimplib && (!config->mingw || !config->implib.empty()))
createImportLibrary(/*asLib=*/false);
assignExportOrdinals();
}
// Handle /output-def (MinGW specific).
if (auto *arg = args.getLastArg(OPT_output_def))
- writeDefFile(arg->getValue());
+ writeDefFile(arg->getValue(), config->exports);
// Set extra alignment for .comm symbols
for (auto pair : config->alignComm) {
StringRef name = pair.first;
uint32_t alignment = pair.second;
- Symbol *sym = symtab->find(name);
+ Symbol *sym = ctx.symtab.find(name);
if (!sym) {
warn("/aligncomm symbol " + name + " not found");
continue;
c->setAlignment(std::max(c->getAlignment(), alignment));
}
- // Windows specific -- Create a side-by-side manifest file.
- if (config->manifest == Configuration::SideBySide)
+ // Windows specific -- Create an embedded or side-by-side manifest.
+ // /manifestdependency: enables /manifest unless an explicit /manifest:no is
+ // also passed.
+ if (config->manifest == Configuration::Embed)
+ addBuffer(createManifestRes(), false, false);
+ else if (config->manifest == Configuration::SideBySide ||
+ (config->manifest == Configuration::Default &&
+ !config->manifestDependencies.empty()))
createSideBySideManifest();
// Handle /order. We want to do this at this moment because we
if (auto *arg = args.getLastArg(OPT_call_graph_ordering_file)) {
parseCallGraphFile(arg->getValue());
}
- readCallGraphsFromObjectFiles();
+ readCallGraphsFromObjectFiles(ctx);
}
// Handle /print-symbol-order.
// For now, just manually try to retain the known possible personality
// functions. This doesn't bring in more object files, but only marks
// functions that already have been included to be retained.
- for (const char *n : {"__gxx_personality_v0", "__gcc_personality_v0"}) {
- Defined *d = dyn_cast_or_null<Defined>(symtab->findUnderscore(n));
+ for (const char *n : {"__gxx_personality_v0", "__gcc_personality_v0",
+ "rust_eh_personality"}) {
+ Defined *d = dyn_cast_or_null<Defined>(ctx.symtab.findUnderscore(n));
if (d && !d->isGCRoot) {
d->isGCRoot = true;
config->gcroot.push_back(d);
}
}
- markLive(symtab->getChunks());
+ markLive(ctx);
}
// Needs to happen after the last call to addFile().
// Identify identical COMDAT sections to merge them.
if (config->doICF != ICFLevel::None) {
- findKeepUniqueSections();
- doICF(symtab->getChunks(), config->doICF);
+ findKeepUniqueSections(ctx);
+ doICF(ctx);
}
// Write the result.
- writeResult();
+ writeResult(ctx);
// Stop early so we can print the results.
rootTimer.stop();
if (config->showTiming)
- Timer::root().print();
+ ctx.rootTimer.print();
}
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#include "SymbolTable.h"
#include "lld/Common/LLVM.h"
#include "lld/Common/Reproduce.h"
-#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Object/Archive.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/TarWriter.h"
+#include "llvm/WindowsDriver/MSVCPaths.h"
#include <memory>
+#include <optional>
#include <set>
#include <vector>
-namespace lld {
-namespace coff {
-
-class LinkerDriver;
-extern LinkerDriver *driver;
+namespace lld::coff {
using llvm::COFF::MachineTypes;
using llvm::COFF::WindowsSubsystem;
-using llvm::Optional;
+using std::optional;
-class COFFOptTable : public llvm::opt::OptTable {
+class COFFOptTable : public llvm::opt::GenericOptTable {
public:
COFFOptTable();
};
-// Constructing the option table is expensive. Use a global table to avoid doing
-// it more than once.
-extern COFFOptTable optTable;
-
// The result of parsing the .drective section. The /export: and /include:
// options are handled separately because they reference symbols, and the number
// of symbols can be quite large. The LLVM Option library will perform at least
struct ParsedDirectives {
std::vector<StringRef> exports;
std::vector<StringRef> includes;
+ std::vector<StringRef> excludes;
llvm::opt::InputArgList args;
};
class ArgParser {
public:
+ ArgParser(COFFLinkerContext &ctx);
+
// Parses command line options.
llvm::opt::InputArgList parse(llvm::ArrayRef<const char *> args);
void addLINK(SmallVector<const char *, 256> &argv);
std::vector<const char *> tokenize(StringRef s);
+
+ COFFLinkerContext &ctx;
};
class LinkerDriver {
public:
+ LinkerDriver(COFFLinkerContext &ctx) : ctx(ctx) {}
+
void linkerMain(llvm::ArrayRef<const char *> args);
+ // Adds various search paths based on the sysroot. Must only be called once
+ // config->machine has been set.
+ void addWinSysRootLibSearchPaths();
+
// Used by the resolver to parse .drectve section contents.
void parseDirectives(InputFile *file);
private:
// Searches a file from search paths.
- Optional<StringRef> findFile(StringRef filename);
- Optional<StringRef> findLib(StringRef filename);
+ std::optional<StringRef> findFile(StringRef filename);
+ std::optional<StringRef> findLib(StringRef filename);
StringRef doFindFile(StringRef filename);
StringRef doFindLib(StringRef filename);
StringRef doFindLibMinGW(StringRef filename);
+ bool findUnderscoreMangle(StringRef sym);
+
+ // Determines the location of the sysroot based on `args`, environment, etc.
+ void detectWinSysRoot(const llvm::opt::InputArgList &args);
+
+ // Symbol names are mangled by prepending "_" on x86.
+ StringRef mangle(StringRef sym);
+
+ llvm::Triple::ArchType getArch();
+
+ uint64_t getDefaultImageBase();
+
+ bool isDecorated(StringRef sym);
+
+ std::string getMapFile(const llvm::opt::InputArgList &args,
+ llvm::opt::OptSpecifier os,
+ llvm::opt::OptSpecifier osFile);
+
+ std::string getImplibPath();
+
+ // The import name is calculated as follows:
+ //
+ // | LIBRARY w/ ext | LIBRARY w/o ext | no LIBRARY
+ // -----+----------------+---------------------+------------------
+ // LINK | {value} | {value}.{.dll/.exe} | {output name}
+ // LIB | {value} | {value}.dll | {output name}.dll
+ //
+ std::string getImportName(bool asLib);
+
+ void createImportLibrary(bool asLib);
+
+ void parseModuleDefs(StringRef path);
+
+ // Parse an /order file. If an option is given, the linker places COMDAT
+ // sections int he same order as their names appear in the given file.
+ void parseOrderFile(StringRef arg);
+
+ void parseCallGraphFile(StringRef path);
+
+ void parsePDBAltPath();
+
// Parses LIB environment which contains a list of search paths.
void addLibSearchPaths();
std::vector<StringRef> filePaths;
std::vector<MemoryBufferRef> resources;
- llvm::StringSet<> directivesExports;
-};
+ llvm::DenseSet<StringRef> directivesExports;
+ llvm::DenseSet<StringRef> excludedSymbols;
+
+ COFFLinkerContext &ctx;
-// Functions below this line are defined in DriverUtils.cpp.
+ llvm::ToolsetLayout vsLayout = llvm::ToolsetLayout::OlderVS;
+ std::string vcToolChainPath;
+ llvm::SmallString<128> diaPath;
+ bool useWinSysRootLibPath = false;
+ llvm::SmallString<128> universalCRTLibPath;
+ int sdkMajor = 0;
+ llvm::SmallString<128> windowsSdkLibPath;
-void printHelp(const char *argv0);
+ // Functions below this line are defined in DriverUtils.cpp.
-// Parses a string in the form of "<integer>[,<integer>]".
-void parseNumbers(StringRef arg, uint64_t *addr, uint64_t *size = nullptr);
+ void printHelp(const char *argv0);
-void parseGuard(StringRef arg);
+ // Parses a string in the form of "<integer>[,<integer>]".
+ void parseNumbers(StringRef arg, uint64_t *addr, uint64_t *size = nullptr);
-// Parses a string in the form of "<integer>[.<integer>]".
-// Minor's default value is 0.
-void parseVersion(StringRef arg, uint32_t *major, uint32_t *minor);
+ void parseGuard(StringRef arg);
-// Parses a string in the form of "<subsystem>[,<integer>[.<integer>]]".
-void parseSubsystem(StringRef arg, WindowsSubsystem *sys, uint32_t *major,
- uint32_t *minor, bool *gotVersion = nullptr);
+ // Parses a string in the form of "<integer>[.<integer>]".
+ // Minor's default value is 0.
+ void parseVersion(StringRef arg, uint32_t *major, uint32_t *minor);
-void parseAlternateName(StringRef);
-void parseMerge(StringRef);
-void parseSection(StringRef);
-void parseAligncomm(StringRef);
+ // Parses a string in the form of "<subsystem>[,<integer>[.<integer>]]".
+ void parseSubsystem(StringRef arg, WindowsSubsystem *sys, uint32_t *major,
+ uint32_t *minor, bool *gotVersion = nullptr);
-// Parses a string in the form of "[:<integer>]"
-void parseFunctionPadMin(llvm::opt::Arg *a, llvm::COFF::MachineTypes machine);
+ void parseAlternateName(StringRef);
+ void parseMerge(StringRef);
+ void parsePDBPageSize(StringRef);
+ void parseSection(StringRef);
+ void parseAligncomm(StringRef);
-// Parses a string in the form of "EMBED[,=<integer>]|NO".
-void parseManifest(StringRef arg);
+ // Parses a string in the form of "[:<integer>]"
+ void parseFunctionPadMin(llvm::opt::Arg *a);
-// Parses a string in the form of "level=<string>|uiAccess=<string>"
-void parseManifestUAC(StringRef arg);
+ // Parses a string in the form of "EMBED[,=<integer>]|NO".
+ void parseManifest(StringRef arg);
-// Parses a string in the form of "cd|net[,(cd|net)]*"
-void parseSwaprun(StringRef arg);
+ // Parses a string in the form of "level=<string>|uiAccess=<string>"
+ void parseManifestUAC(StringRef arg);
-// Create a resource file containing a manifest XML.
-std::unique_ptr<MemoryBuffer> createManifestRes();
-void createSideBySideManifest();
+ // Parses a string in the form of "cd|net[,(cd|net)]*"
+ void parseSwaprun(StringRef arg);
-// Used for dllexported symbols.
-Export parseExport(StringRef arg);
-void fixupExports();
-void assignExportOrdinals();
+ // Create a resource file containing a manifest XML.
+ std::unique_ptr<MemoryBuffer> createManifestRes();
+ void createSideBySideManifest();
+ std::string createDefaultXml();
+ std::string createManifestXmlWithInternalMt(StringRef defaultXml);
+ std::string createManifestXmlWithExternalMt(StringRef defaultXml);
+ std::string createManifestXml();
-// Parses a string in the form of "key=value" and check
-// if value matches previous values for the key.
-// This feature used in the directive section to reject
-// incompatible objects.
-void checkFailIfMismatch(StringRef arg, InputFile *source);
+ std::unique_ptr<llvm::WritableMemoryBuffer>
+ createMemoryBufferForManifestRes(size_t manifestRes);
-// Convert Windows resource files (.res files) to a .obj file.
-MemoryBufferRef convertResToCOFF(ArrayRef<MemoryBufferRef> mbs,
- ArrayRef<ObjFile *> objs);
+ // Used for dllexported symbols.
+ Export parseExport(StringRef arg);
+ void fixupExports();
+ void assignExportOrdinals();
+
+ // Parses a string in the form of "key=value" and check
+ // if value matches previous values for the key.
+ // This feature used in the directive section to reject
+ // incompatible objects.
+ void checkFailIfMismatch(StringRef arg, InputFile *source);
+
+ // Convert Windows resource files (.res files) to a .obj file.
+ MemoryBufferRef convertResToCOFF(ArrayRef<MemoryBufferRef> mbs,
+ ArrayRef<ObjFile *> objs);
+};
// Create enum with OPT_xxx values for each option in Options.td
enum {
#undef OPTION
};
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
//
//===----------------------------------------------------------------------===//
-#include "Config.h"
+#include "COFFLinkerContext.h"
#include "Driver.h"
#include "Symbols.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
-#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/BinaryFormat/COFF.h"
#include "llvm/Object/COFF.h"
#include "llvm/WindowsManifest/WindowsManifestMerger.h"
#include <limits>
#include <memory>
+#include <optional>
using namespace llvm::COFF;
using namespace llvm;
class Executor {
public:
- explicit Executor(StringRef s) : prog(saver.save(s)) {}
- void add(StringRef s) { args.push_back(saver.save(s)); }
- void add(std::string &s) { args.push_back(saver.save(s)); }
- void add(Twine s) { args.push_back(saver.save(s)); }
- void add(const char *s) { args.push_back(saver.save(s)); }
+ explicit Executor(StringRef s) : prog(saver().save(s)) {}
+ void add(StringRef s) { args.push_back(saver().save(s)); }
+ void add(std::string &s) { args.push_back(saver().save(s)); }
+ void add(Twine s) { args.push_back(saver().save(s)); }
+ void add(const char *s) { args.push_back(saver().save(s)); }
void run() {
ErrorOr<std::string> exeOrErr = sys::findProgramByName(prog);
if (auto ec = exeOrErr.getError())
fatal("unable to find " + prog + " in PATH: " + ec.message());
- StringRef exe = saver.save(*exeOrErr);
+ StringRef exe = saver().save(*exeOrErr);
args.insert(args.begin(), exe);
if (sys::ExecuteAndWait(args[0], args) != 0)
} // anonymous namespace
// Parses a string in the form of "<integer>[,<integer>]".
-void parseNumbers(StringRef arg, uint64_t *addr, uint64_t *size) {
- StringRef s1, s2;
- std::tie(s1, s2) = arg.split(',');
+void LinkerDriver::parseNumbers(StringRef arg, uint64_t *addr, uint64_t *size) {
+ auto [s1, s2] = arg.split(',');
if (s1.getAsInteger(0, *addr))
fatal("invalid number: " + s1);
if (size && !s2.empty() && s2.getAsInteger(0, *size))
// Parses a string in the form of "<integer>[.<integer>]".
// If second number is not present, Minor is set to 0.
-void parseVersion(StringRef arg, uint32_t *major, uint32_t *minor) {
- StringRef s1, s2;
- std::tie(s1, s2) = arg.split('.');
+void LinkerDriver::parseVersion(StringRef arg, uint32_t *major,
+ uint32_t *minor) {
+ auto [s1, s2] = arg.split('.');
if (s1.getAsInteger(10, *major))
fatal("invalid number: " + s1);
*minor = 0;
fatal("invalid number: " + s2);
}
-void parseGuard(StringRef fullArg) {
+void LinkerDriver::parseGuard(StringRef fullArg) {
SmallVector<StringRef, 1> splitArgs;
fullArg.split(splitArgs, ",");
for (StringRef arg : splitArgs) {
if (arg.equals_insensitive("no"))
- config->guardCF = GuardCFLevel::Off;
+ ctx.config.guardCF = GuardCFLevel::Off;
else if (arg.equals_insensitive("nolongjmp"))
- config->guardCF &= ~GuardCFLevel::LongJmp;
+ ctx.config.guardCF &= ~GuardCFLevel::LongJmp;
else if (arg.equals_insensitive("noehcont"))
- config->guardCF &= ~GuardCFLevel::EHCont;
- else if (arg.equals_insensitive("cf"))
- config->guardCF = GuardCFLevel::CF;
- else if (arg.equals_insensitive("longjmp"))
- config->guardCF |= GuardCFLevel::CF | GuardCFLevel::LongJmp;
+ ctx.config.guardCF &= ~GuardCFLevel::EHCont;
+ else if (arg.equals_insensitive("cf") || arg.equals_insensitive("longjmp"))
+ ctx.config.guardCF |= GuardCFLevel::CF | GuardCFLevel::LongJmp;
else if (arg.equals_insensitive("ehcont"))
- config->guardCF |= GuardCFLevel::CF | GuardCFLevel::EHCont;
+ ctx.config.guardCF |= GuardCFLevel::CF | GuardCFLevel::EHCont;
else
fatal("invalid argument to /guard: " + arg);
}
}
// Parses a string in the form of "<subsystem>[,<integer>[.<integer>]]".
-void parseSubsystem(StringRef arg, WindowsSubsystem *sys, uint32_t *major,
- uint32_t *minor, bool *gotVersion) {
- StringRef sysStr, ver;
- std::tie(sysStr, ver) = arg.split(',');
+void LinkerDriver::parseSubsystem(StringRef arg, WindowsSubsystem *sys,
+ uint32_t *major, uint32_t *minor,
+ bool *gotVersion) {
+ auto [sysStr, ver] = arg.split(',');
std::string sysStrLower = sysStr.lower();
*sys = StringSwitch<WindowsSubsystem>(sysStrLower)
.Case("boot_application", IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION)
// Parse a string of the form of "<from>=<to>".
// Results are directly written to Config.
-void parseAlternateName(StringRef s) {
- StringRef from, to;
- std::tie(from, to) = s.split('=');
+void LinkerDriver::parseAlternateName(StringRef s) {
+ auto [from, to] = s.split('=');
if (from.empty() || to.empty())
fatal("/alternatename: invalid argument: " + s);
- auto it = config->alternateNames.find(from);
- if (it != config->alternateNames.end() && it->second != to)
+ auto it = ctx.config.alternateNames.find(from);
+ if (it != ctx.config.alternateNames.end() && it->second != to)
fatal("/alternatename: conflicts: " + s);
- config->alternateNames.insert(it, std::make_pair(from, to));
+ ctx.config.alternateNames.insert(it, std::make_pair(from, to));
}
// Parse a string of the form of "<from>=<to>".
// Results are directly written to Config.
-void parseMerge(StringRef s) {
- StringRef from, to;
- std::tie(from, to) = s.split('=');
+void LinkerDriver::parseMerge(StringRef s) {
+ auto [from, to] = s.split('=');
if (from.empty() || to.empty())
fatal("/merge: invalid argument: " + s);
if (from == ".rsrc" || to == ".rsrc")
fatal("/merge: cannot merge '.rsrc' with any section");
if (from == ".reloc" || to == ".reloc")
fatal("/merge: cannot merge '.reloc' with any section");
- auto pair = config->merge.insert(std::make_pair(from, to));
+ auto pair = ctx.config.merge.insert(std::make_pair(from, to));
bool inserted = pair.second;
if (!inserted) {
StringRef existing = pair.first->second;
}
}
+void LinkerDriver::parsePDBPageSize(StringRef s) {
+ int v;
+ if (s.getAsInteger(0, v)) {
+ error("/pdbpagesize: invalid argument: " + s);
+ return;
+ }
+ if (v != 4096 && v != 8192 && v != 16384 && v != 32768) {
+ error("/pdbpagesize: invalid argument: " + s);
+ return;
+ }
+
+ ctx.config.pdbPageSize = v;
+}
+
static uint32_t parseSectionAttributes(StringRef s) {
uint32_t ret = 0;
for (char c : s.lower()) {
}
// Parses /section option argument.
-void parseSection(StringRef s) {
- StringRef name, attrs;
- std::tie(name, attrs) = s.split(',');
+void LinkerDriver::parseSection(StringRef s) {
+ auto [name, attrs] = s.split(',');
if (name.empty() || attrs.empty())
fatal("/section: invalid argument: " + s);
- config->section[name] = parseSectionAttributes(attrs);
+ ctx.config.section[name] = parseSectionAttributes(attrs);
}
// Parses /aligncomm option argument.
-void parseAligncomm(StringRef s) {
- StringRef name, align;
- std::tie(name, align) = s.split(',');
+void LinkerDriver::parseAligncomm(StringRef s) {
+ auto [name, align] = s.split(',');
if (name.empty() || align.empty()) {
error("/aligncomm: invalid argument: " + s);
return;
error("/aligncomm: invalid argument: " + s);
return;
}
- config->alignComm[std::string(name)] =
- std::max(config->alignComm[std::string(name)], 1 << v);
+ ctx.config.alignComm[std::string(name)] =
+ std::max(ctx.config.alignComm[std::string(name)], 1 << v);
}
// Parses /functionpadmin option argument.
-void parseFunctionPadMin(llvm::opt::Arg *a, llvm::COFF::MachineTypes machine) {
+void LinkerDriver::parseFunctionPadMin(llvm::opt::Arg *a) {
StringRef arg = a->getNumValues() ? a->getValue() : "";
if (!arg.empty()) {
// Optional padding in bytes is given.
- if (arg.getAsInteger(0, config->functionPadMin))
+ if (arg.getAsInteger(0, ctx.config.functionPadMin))
error("/functionpadmin: invalid argument: " + arg);
return;
}
// No optional argument given.
// Set default padding based on machine, similar to link.exe.
// There is no default padding for ARM platforms.
- if (machine == I386) {
- config->functionPadMin = 5;
- } else if (machine == AMD64) {
- config->functionPadMin = 6;
+ if (ctx.config.machine == I386) {
+ ctx.config.functionPadMin = 5;
+ } else if (ctx.config.machine == AMD64) {
+ ctx.config.functionPadMin = 6;
} else {
error("/functionpadmin: invalid argument for this machine: " + arg);
}
}
// Parses a string in the form of "EMBED[,=<integer>]|NO".
-// Results are directly written to Config.
-void parseManifest(StringRef arg) {
+// Results are directly written to
+// Config.
+void LinkerDriver::parseManifest(StringRef arg) {
if (arg.equals_insensitive("no")) {
- config->manifest = Configuration::No;
+ ctx.config.manifest = Configuration::No;
return;
}
if (!arg.startswith_insensitive("embed"))
fatal("invalid option " + arg);
- config->manifest = Configuration::Embed;
+ ctx.config.manifest = Configuration::Embed;
arg = arg.substr(strlen("embed"));
if (arg.empty())
return;
if (!arg.startswith_insensitive(",id="))
fatal("invalid option " + arg);
arg = arg.substr(strlen(",id="));
- if (arg.getAsInteger(0, config->manifestID))
+ if (arg.getAsInteger(0, ctx.config.manifestID))
fatal("invalid option " + arg);
}
// Parses a string in the form of "level=<string>|uiAccess=<string>|NO".
// Results are directly written to Config.
-void parseManifestUAC(StringRef arg) {
+void LinkerDriver::parseManifestUAC(StringRef arg) {
if (arg.equals_insensitive("no")) {
- config->manifestUAC = false;
+ ctx.config.manifestUAC = false;
return;
}
for (;;) {
return;
if (arg.startswith_insensitive("level=")) {
arg = arg.substr(strlen("level="));
- std::tie(config->manifestLevel, arg) = arg.split(" ");
+ std::tie(ctx.config.manifestLevel, arg) = arg.split(" ");
continue;
}
if (arg.startswith_insensitive("uiaccess=")) {
arg = arg.substr(strlen("uiaccess="));
- std::tie(config->manifestUIAccess, arg) = arg.split(" ");
+ std::tie(ctx.config.manifestUIAccess, arg) = arg.split(" ");
continue;
}
fatal("invalid option " + arg);
// Parses a string in the form of "cd|net[,(cd|net)]*"
// Results are directly written to Config.
-void parseSwaprun(StringRef arg) {
+void LinkerDriver::parseSwaprun(StringRef arg) {
do {
- StringRef swaprun, newArg;
- std::tie(swaprun, newArg) = arg.split(',');
+ auto [swaprun, newArg] = arg.split(',');
if (swaprun.equals_insensitive("cd"))
- config->swaprunCD = true;
+ ctx.config.swaprunCD = true;
else if (swaprun.equals_insensitive("net"))
- config->swaprunNet = true;
+ ctx.config.swaprunNet = true;
else if (swaprun.empty())
error("/swaprun: missing argument");
else
};
}
-static std::string createDefaultXml() {
+std::string LinkerDriver::createDefaultXml() {
std::string ret;
raw_string_ostream os(ret);
os << "<?xml version=\"1.0\" standalone=\"yes\"?>\n"
<< "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\"\n"
<< " manifestVersion=\"1.0\">\n";
- if (config->manifestUAC) {
+ if (ctx.config.manifestUAC) {
os << " <trustInfo>\n"
<< " <security>\n"
<< " <requestedPrivileges>\n"
- << " <requestedExecutionLevel level=" << config->manifestLevel
- << " uiAccess=" << config->manifestUIAccess << "/>\n"
+ << " <requestedExecutionLevel level=" << ctx.config.manifestLevel
+ << " uiAccess=" << ctx.config.manifestUIAccess << "/>\n"
<< " </requestedPrivileges>\n"
<< " </security>\n"
<< " </trustInfo>\n";
}
- if (!config->manifestDependency.empty()) {
+ for (auto manifestDependency : ctx.config.manifestDependencies) {
os << " <dependency>\n"
<< " <dependentAssembly>\n"
- << " <assemblyIdentity " << config->manifestDependency << " />\n"
+ << " <assemblyIdentity " << manifestDependency << " />\n"
<< " </dependentAssembly>\n"
<< " </dependency>\n";
}
return os.str();
}
-static std::string createManifestXmlWithInternalMt(StringRef defaultXml) {
+std::string
+LinkerDriver::createManifestXmlWithInternalMt(StringRef defaultXml) {
std::unique_ptr<MemoryBuffer> defaultXmlCopy =
MemoryBuffer::getMemBufferCopy(defaultXml);
fatal("internal manifest tool failed on default xml: " +
toString(std::move(e)));
- for (StringRef filename : config->manifestInput) {
+ for (StringRef filename : ctx.config.manifestInput) {
std::unique_ptr<MemoryBuffer> manifest =
check(MemoryBuffer::getFile(filename));
- if (auto e = merger.merge(*manifest.get()))
+ // Call takeBuffer to include in /reproduce: output if applicable.
+ if (auto e = merger.merge(takeBuffer(std::move(manifest))))
fatal("internal manifest tool failed on file " + filename + ": " +
toString(std::move(e)));
}
return std::string(merger.getMergedManifest().get()->getBuffer());
}
-static std::string createManifestXmlWithExternalMt(StringRef defaultXml) {
+std::string
+LinkerDriver::createManifestXmlWithExternalMt(StringRef defaultXml) {
// Create the default manifest file as a temporary file.
TemporaryFile Default("defaultxml", "manifest");
std::error_code ec;
Executor e("mt.exe");
e.add("/manifest");
e.add(Default.path);
- for (StringRef filename : config->manifestInput) {
+ for (StringRef filename : ctx.config.manifestInput) {
e.add("/manifest");
e.add(filename);
+
+ // Manually add the file to the /reproduce: tar if needed.
+ if (tar)
+ if (auto mbOrErr = MemoryBuffer::getFile(filename))
+ takeBuffer(std::move(*mbOrErr));
}
e.add("/nologo");
e.add("/out:" + StringRef(user.path));
->getBuffer());
}
-static std::string createManifestXml() {
+std::string LinkerDriver::createManifestXml() {
std::string defaultXml = createDefaultXml();
- if (config->manifestInput.empty())
+ if (ctx.config.manifestInput.empty())
return defaultXml;
if (windows_manifest::isAvailable())
return createManifestXmlWithExternalMt(defaultXml);
}
-static std::unique_ptr<WritableMemoryBuffer>
-createMemoryBufferForManifestRes(size_t manifestSize) {
+std::unique_ptr<WritableMemoryBuffer>
+LinkerDriver::createMemoryBufferForManifestRes(size_t manifestSize) {
size_t resSize = alignTo(
object::WIN_RES_MAGIC_SIZE + object::WIN_RES_NULL_ENTRY_SIZE +
sizeof(object::WinResHeaderPrefix) + sizeof(object::WinResIDs) +
sizeof(object::WinResHeaderSuffix) + manifestSize,
object::WIN_RES_DATA_ALIGNMENT);
- return WritableMemoryBuffer::getNewMemBuffer(resSize, config->outputFile +
+ return WritableMemoryBuffer::getNewMemBuffer(resSize, ctx.config.outputFile +
".manifest.res");
}
buf += object::WIN_RES_NULL_ENTRY_SIZE;
}
-static void writeResEntryHeader(char *&buf, size_t manifestSize) {
+static void writeResEntryHeader(char *&buf, size_t manifestSize,
+ int manifestID) {
// Write the prefix.
auto *prefix = reinterpret_cast<object::WinResHeaderPrefix *>(buf);
prefix->DataSize = manifestSize;
// Write the Type/Name IDs.
auto *iDs = reinterpret_cast<object::WinResIDs *>(buf);
iDs->setType(RT_MANIFEST);
- iDs->setName(config->manifestID);
+ iDs->setName(manifestID);
buf += sizeof(object::WinResIDs);
// Write the suffix.
}
// Create a resource file containing a manifest XML.
-std::unique_ptr<MemoryBuffer> createManifestRes() {
+std::unique_ptr<MemoryBuffer> LinkerDriver::createManifestRes() {
std::string manifest = createManifestXml();
std::unique_ptr<WritableMemoryBuffer> res =
char *buf = res->getBufferStart();
writeResFileHeader(buf);
- writeResEntryHeader(buf, manifest.size());
+ writeResEntryHeader(buf, manifest.size(), ctx.config.manifestID);
// Copy the manifest data into the .res file.
std::copy(manifest.begin(), manifest.end(), buf);
return std::move(res);
}
-void createSideBySideManifest() {
- std::string path = std::string(config->manifestFile);
+void LinkerDriver::createSideBySideManifest() {
+ std::string path = std::string(ctx.config.manifestFile);
if (path == "")
- path = config->outputFile + ".manifest";
+ path = ctx.config.outputFile + ".manifest";
std::error_code ec;
raw_fd_ostream out(path, ec, sys::fs::OF_TextWithCRLF);
if (ec)
// "<name>[=<internalname>][,@ordinal[,NONAME]][,DATA][,PRIVATE]"
// or "<name>=<dllname>.<name>".
// Used for parsing /export arguments.
-Export parseExport(StringRef arg) {
+Export LinkerDriver::parseExport(StringRef arg) {
Export e;
StringRef rest;
std::tie(e.name, rest) = arg.split(",");
goto err;
if (e.name.contains('=')) {
- StringRef x, y;
- std::tie(x, y) = e.name.split("=");
+ auto [x, y] = e.name.split("=");
// If "<name>=<dllname>.<name>".
if (y.contains(".")) {
fatal("invalid /export: " + arg);
}
-static StringRef undecorate(StringRef sym) {
- if (config->machine != I386)
+static StringRef undecorate(COFFLinkerContext &ctx, StringRef sym) {
+ if (ctx.config.machine != I386)
return sym;
// In MSVC mode, a fully decorated stdcall function is exported
// as-is with the leading underscore (with type IMPORT_NAME).
// In MinGW mode, a decorated stdcall function gets the underscore
// removed, just like normal cdecl functions.
- if (sym.startswith("_") && sym.contains('@') && !config->mingw)
+ if (sym.startswith("_") && sym.contains('@') && !ctx.config.mingw)
return sym;
return sym.startswith("_") ? sym.substr(1) : sym;
}
sym = sym.substr(0, sym.find('@', 1));
if (!sym.startswith("@")) {
if (prefix && !sym.startswith("_"))
- return saver.save("_" + sym);
+ return saver().save("_" + sym);
return sym;
}
// For fastcall, remove the leading @ and replace it with an
// underscore, if prefixes are used.
sym = sym.substr(1);
if (prefix)
- sym = saver.save("_" + sym);
+ sym = saver().save("_" + sym);
return sym;
}
// Performs error checking on all /export arguments.
// It also sets ordinals.
-void fixupExports() {
+void LinkerDriver::fixupExports() {
// Symbol ordinals must be unique.
std::set<uint16_t> ords;
- for (Export &e : config->exports) {
+ for (Export &e : ctx.config.exports) {
if (e.ordinal == 0)
continue;
if (!ords.insert(e.ordinal).second)
fatal("duplicate export ordinal: " + e.name);
}
- for (Export &e : config->exports) {
+ for (Export &e : ctx.config.exports) {
if (!e.forwardTo.empty()) {
- e.exportName = undecorate(e.name);
+ e.exportName = undecorate(ctx, e.name);
} else {
- e.exportName = undecorate(e.extName.empty() ? e.name : e.extName);
+ e.exportName = undecorate(ctx, e.extName.empty() ? e.name : e.extName);
}
}
- if (config->killAt && config->machine == I386) {
- for (Export &e : config->exports) {
+ if (ctx.config.killAt && ctx.config.machine == I386) {
+ for (Export &e : ctx.config.exports) {
e.name = killAt(e.name, true);
e.exportName = killAt(e.exportName, false);
e.extName = killAt(e.extName, true);
}
// Uniquefy by name.
- DenseMap<StringRef, Export *> map(config->exports.size());
+ DenseMap<StringRef, Export *> map(ctx.config.exports.size());
std::vector<Export> v;
- for (Export &e : config->exports) {
+ for (Export &e : ctx.config.exports) {
auto pair = map.insert(std::make_pair(e.exportName, &e));
bool inserted = pair.second;
if (inserted) {
continue;
warn("duplicate /export option: " + e.name);
}
- config->exports = std::move(v);
+ ctx.config.exports = std::move(v);
// Sort by name.
- std::sort(config->exports.begin(), config->exports.end(),
- [](const Export &a, const Export &b) {
- return a.exportName < b.exportName;
- });
+ llvm::sort(ctx.config.exports, [](const Export &a, const Export &b) {
+ return a.exportName < b.exportName;
+ });
}
-void assignExportOrdinals() {
+void LinkerDriver::assignExportOrdinals() {
// Assign unique ordinals if default (= 0).
uint32_t max = 0;
- for (Export &e : config->exports)
+ for (Export &e : ctx.config.exports)
max = std::max(max, (uint32_t)e.ordinal);
- for (Export &e : config->exports)
+ for (Export &e : ctx.config.exports)
if (e.ordinal == 0)
e.ordinal = ++max;
if (max > std::numeric_limits<uint16_t>::max())
- fatal("too many exported symbols (max " +
+ fatal("too many exported symbols (got " + Twine(max) + ", max " +
Twine(std::numeric_limits<uint16_t>::max()) + ")");
}
// Parses a string in the form of "key=value" and check
// if value matches previous values for the same key.
-void checkFailIfMismatch(StringRef arg, InputFile *source) {
- StringRef k, v;
- std::tie(k, v) = arg.split('=');
+void LinkerDriver::checkFailIfMismatch(StringRef arg, InputFile *source) {
+ auto [k, v] = arg.split('=');
if (k.empty() || v.empty())
fatal("/failifmismatch: invalid argument: " + arg);
- std::pair<StringRef, InputFile *> existing = config->mustMatch[k];
+ std::pair<StringRef, InputFile *> existing = ctx.config.mustMatch[k];
if (!existing.first.empty() && v != existing.first) {
std::string sourceStr = source ? toString(source) : "cmd-line";
std::string existingStr =
existingStr + " has value " + existing.first + "\n>>> " + sourceStr +
" has value " + v);
}
- config->mustMatch[k] = {v, source};
+ ctx.config.mustMatch[k] = {v, source};
}
// Convert Windows resource files (.res files) to a .obj file.
// Does what cvtres.exe does, but in-process and cross-platform.
-MemoryBufferRef convertResToCOFF(ArrayRef<MemoryBufferRef> mbs,
- ArrayRef<ObjFile *> objs) {
- object::WindowsResourceParser parser(/* MinGW */ config->mingw);
+MemoryBufferRef LinkerDriver::convertResToCOFF(ArrayRef<MemoryBufferRef> mbs,
+ ArrayRef<ObjFile *> objs) {
+ object::WindowsResourceParser parser(/* MinGW */ ctx.config.mingw);
std::vector<std::string> duplicates;
for (MemoryBufferRef mb : mbs) {
fatal(toString(std::move(ec)));
}
- if (config->mingw)
+ if (ctx.config.mingw)
parser.cleanUpManifests(duplicates);
for (const auto &dupeDiag : duplicates)
- if (config->forceMultipleRes)
+ if (ctx.config.forceMultipleRes)
warn(dupeDiag);
else
error(dupeDiag);
Expected<std::unique_ptr<MemoryBuffer>> e =
- llvm::object::writeWindowsResourceCOFF(config->machine, parser,
- config->timestamp);
+ llvm::object::writeWindowsResourceCOFF(ctx.config.machine, parser,
+ ctx.config.timestamp);
if (!e)
fatal("failed to write .res to COFF: " + toString(e.takeError()));
// Create OptTable
// Create prefix string literals used in Options.td
-#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
+#define PREFIX(NAME, VALUE) \
+ static constexpr llvm::StringLiteral NAME##_init[] = VALUE; \
+ static constexpr llvm::ArrayRef<llvm::StringLiteral> NAME( \
+ NAME##_init, std::size(NAME##_init) - 1);
#include "Options.inc"
#undef PREFIX
// Create table mapping all options defined in Options.td
-static const llvm::opt::OptTable::Info infoTable[] = {
+static constexpr llvm::opt::OptTable::Info infoTable[] = {
#define OPTION(X1, X2, ID, KIND, GROUP, ALIAS, X7, X8, X9, X10, X11, X12) \
{X1, X2, X10, X11, OPT_##ID, llvm::opt::Option::KIND##Class, \
X9, X8, OPT_##GROUP, OPT_##ALIAS, X7, X12},
#undef OPTION
};
-COFFOptTable::COFFOptTable() : OptTable(infoTable, true) {}
-
-COFFOptTable optTable;
+COFFOptTable::COFFOptTable() : GenericOptTable(infoTable, true) {}
// Set color diagnostics according to --color-diagnostics={auto,always,never}
// or --no-color-diagnostics flags.
return cl::TokenizeWindowsCommandLine;
}
+ArgParser::ArgParser(COFFLinkerContext &c) : ctx(c) {}
+
// Parses a given list of options.
opt::InputArgList ArgParser::parse(ArrayRef<const char *> argv) {
// Make InputArgList from string vectors.
// options so we parse here before and ignore all the options but
// --rsp-quoting and /lldignoreenv.
// (This means --rsp-quoting can't be added through %LINK%.)
- opt::InputArgList args = optTable.ParseArgs(argv, missingIndex, missingCount);
+ opt::InputArgList args =
+ ctx.optTable.ParseArgs(argv, missingIndex, missingCount);
// Expand response files (arguments in the form of @<filename>) and insert
// flags from %LINK% and %_LINK_%, and then parse the argument again.
argv.data() + argv.size());
if (!args.hasArg(OPT_lldignoreenv))
addLINK(expandedArgv);
- cl::ExpandResponseFiles(saver, getQuotingStyle(args), expandedArgv);
- args = optTable.ParseArgs(makeArrayRef(expandedArgv).drop_front(),
- missingIndex, missingCount);
+ cl::ExpandResponseFiles(saver(), getQuotingStyle(args), expandedArgv);
+ args = ctx.optTable.ParseArgs(ArrayRef(expandedArgv).drop_front(),
+ missingIndex, missingCount);
// Print the real command line if response files are expanded.
if (args.hasArg(OPT_verbose) && argv.size() != expandedArgv.size()) {
}
// Save the command line after response file expansion so we can write it to
- // the PDB if necessary.
- config->argv = {expandedArgv.begin(), expandedArgv.end()};
+ // the PDB if necessary. Mimic MSVC, which skips input files.
+ ctx.config.argv = {argv[0]};
+ for (opt::Arg *arg : args) {
+ if (arg->getOption().getKind() != opt::Option::InputClass) {
+ ctx.config.argv.push_back(args.getArgString(arg->getIndex()));
+ }
+ }
// Handle /WX early since it converts missing argument warnings to errors.
errorHandler().fatalWarnings = args.hasFlag(OPT_WX, OPT_WX_no, false);
for (opt::Arg *arg : args.filtered(OPT_UNKNOWN)) {
std::string nearest;
- if (optTable.findNearest(arg->getAsString(args), nearest) > 1)
+ if (ctx.optTable.findNearest(arg->getAsString(args), nearest) > 1)
warn("ignoring unknown argument '" + arg->getAsString(args) + "'");
else
warn("ignoring unknown argument '" + arg->getAsString(args) +
// Handle /EXPORT and /INCLUDE in a fast path. These directives can appear for
// potentially every symbol in the object, so they must be handled quickly.
SmallVector<StringRef, 16> tokens;
- cl::TokenizeWindowsCommandLineNoCopy(s, saver, tokens);
+ cl::TokenizeWindowsCommandLineNoCopy(s, saver(), tokens);
for (StringRef tok : tokens) {
if (tok.startswith_insensitive("/export:") ||
tok.startswith_insensitive("-export:"))
else if (tok.startswith_insensitive("/include:") ||
tok.startswith_insensitive("-include:"))
result.includes.push_back(tok.substr(strlen("/include:")));
+ else if (tok.startswith_insensitive("/exclude-symbols:") ||
+ tok.startswith_insensitive("-exclude-symbols:"))
+ result.excludes.push_back(tok.substr(strlen("/exclude-symbols:")));
else {
// Copy substrings that are not valid C strings. The tokenizer may have
// already copied quoted arguments for us, so those do not need to be
// copied again.
bool HasNul = tok.end() != s.end() && tok.data()[tok.size()] == '\0';
- rest.push_back(HasNul ? tok.data() : saver.save(tok).data());
+ rest.push_back(HasNul ? tok.data() : saver().save(tok).data());
}
}
unsigned missingIndex;
unsigned missingCount;
- result.args = optTable.ParseArgs(rest, missingIndex, missingCount);
+ result.args = ctx.optTable.ParseArgs(rest, missingIndex, missingCount);
if (missingCount)
fatal(Twine(result.args.getArgString(missingIndex)) + ": missing argument");
// So you can pass extra arguments using them.
void ArgParser::addLINK(SmallVector<const char *, 256> &argv) {
// Concatenate LINK env and command line arguments, and then parse them.
- if (Optional<std::string> s = Process::GetEnv("LINK")) {
+ if (std::optional<std::string> s = Process::GetEnv("LINK")) {
std::vector<const char *> v = tokenize(*s);
argv.insert(std::next(argv.begin()), v.begin(), v.end());
}
- if (Optional<std::string> s = Process::GetEnv("_LINK_")) {
+ if (std::optional<std::string> s = Process::GetEnv("_LINK_")) {
std::vector<const char *> v = tokenize(*s);
argv.insert(std::next(argv.begin()), v.begin(), v.end());
}
std::vector<const char *> ArgParser::tokenize(StringRef s) {
SmallVector<const char *, 16> tokens;
- cl::TokenizeWindowsCommandLine(s, saver, tokens);
+ cl::TokenizeWindowsCommandLine(s, saver(), tokens);
return std::vector<const char *>(tokens.begin(), tokens.end());
}
-void printHelp(const char *argv0) {
- optTable.printHelp(lld::outs(),
- (std::string(argv0) + " [options] file...").c_str(),
- "LLVM Linker", false);
+void LinkerDriver::printHelp(const char *argv0) {
+ ctx.optTable.printHelp(lld::outs(),
+ (std::string(argv0) + " [options] file...").c_str(),
+ "LLVM Linker", false);
}
} // namespace coff
//===----------------------------------------------------------------------===//
#include "ICF.h"
+#include "COFFLinkerContext.h"
#include "Chunks.h"
#include "Symbols.h"
#include "lld/Common/ErrorHandler.h"
using namespace llvm;
-namespace lld {
-namespace coff {
-
-static Timer icfTimer("ICF", Timer::root());
+namespace lld::coff {
class ICF {
public:
- ICF(ICFLevel icfLevel) : icfLevel(icfLevel){};
- void run(ArrayRef<Chunk *> v);
+ ICF(COFFLinkerContext &c) : ctx(c){};
+ void run();
private:
void segregate(size_t begin, size_t end, bool constant);
std::vector<SectionChunk *> chunks;
int cnt = 0;
std::atomic<bool> repeat = {false};
- ICFLevel icfLevel = ICFLevel::All;
+
+ COFFLinkerContext &ctx;
};
// Returns true if section S is subject of ICF.
return false;
// Under regular (not safe) ICF, all code sections are eligible.
- if ((icfLevel == ICFLevel::All) &&
+ if ((ctx.config.doICF == ICFLevel::All) &&
c->getOutputCharacteristics() & llvm::COFF::IMAGE_SCN_MEM_EXECUTE)
return true;
size_t boundaries[numShards + 1];
boundaries[0] = 0;
boundaries[numShards] = chunks.size();
- parallelForEachN(1, numShards, [&](size_t i) {
+ parallelFor(1, numShards, [&](size_t i) {
boundaries[i] = findBoundary((i - 1) * step, chunks.size());
});
- parallelForEachN(1, numShards + 1, [&](size_t i) {
+ parallelFor(1, numShards + 1, [&](size_t i) {
if (boundaries[i - 1] < boundaries[i]) {
forEachClassRange(boundaries[i - 1], boundaries[i], fn);
}
// Merge identical COMDAT sections.
// Two sections are considered the same if their section headers,
// contents and relocations are all the same.
-void ICF::run(ArrayRef<Chunk *> vec) {
- ScopedTimer t(icfTimer);
+void ICF::run() {
+ ScopedTimer t(ctx.icfTimer);
// Collect only mergeable sections and group by hash value.
uint32_t nextId = 1;
- for (Chunk *c : vec) {
+ for (Chunk *c : ctx.symtab.getChunks()) {
if (auto *sc = dyn_cast<SectionChunk>(c)) {
if (isEligible(sc))
chunks.push_back(sc);
// Make sure that ICF doesn't merge sections that are being handled by string
// tail merging.
- for (MergeChunk *mc : MergeChunk::instances)
+ for (MergeChunk *mc : ctx.mergeChunkInstances)
if (mc)
for (SectionChunk *sc : mc->sections)
sc->eqClass[0] = nextId++;
}
// Entry point to ICF.
-void doICF(ArrayRef<Chunk *> chunks, ICFLevel icfLevel) {
- ICF(icfLevel).run(chunks);
-}
+void doICF(COFFLinkerContext &ctx) { ICF(ctx).run(); }
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#include "lld/Common/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
-namespace lld {
-namespace coff {
+namespace lld::coff {
-class Chunk;
+class COFFLinkerContext;
-void doICF(ArrayRef<Chunk *> chunks, ICFLevel);
+void doICF(COFFLinkerContext &ctx);
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
//===----------------------------------------------------------------------===//
#include "InputFiles.h"
+#include "COFFLinkerContext.h"
#include "Chunks.h"
#include "Config.h"
#include "DebugTypes.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "lld/Common/DWARF.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
#include "llvm-c/lto.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Support/Path.h"
#include "llvm/Target/TargetOptions.h"
#include <cstring>
+#include <optional>
#include <system_error>
#include <utility>
.str();
}
-std::vector<ObjFile *> ObjFile::instances;
-std::map<std::string, PDBInputFile *> PDBInputFile::instances;
-std::vector<ImportFile *> ImportFile::instances;
-std::vector<BitcodeFile *> BitcodeFile::instances;
-
/// Checks that Source is compatible with being a weak alias to Target.
/// If Source is Undefined and has no weak alias set, makes it a weak
/// alias to Target.
-static void checkAndSetWeakAlias(SymbolTable *symtab, InputFile *f,
+static void checkAndSetWeakAlias(COFFLinkerContext &ctx, InputFile *f,
Symbol *source, Symbol *target) {
if (auto *u = dyn_cast<Undefined>(source)) {
if (u->weakAlias && u->weakAlias != target) {
// of another symbol emitted near the weak symbol.
// Just use the definition from the first object file that defined
// this weak symbol.
- if (config->mingw)
+ if (ctx.config.mingw)
return;
- symtab->reportDuplicate(source, f);
+ ctx.symtab.reportDuplicate(source, f);
}
u->weakAlias = target;
}
return name == "@feat.00" || name == "@comp.id";
}
-ArchiveFile::ArchiveFile(MemoryBufferRef m) : InputFile(ArchiveKind, m) {}
+ArchiveFile::ArchiveFile(COFFLinkerContext &ctx, MemoryBufferRef m)
+ : InputFile(ctx, ArchiveKind, m) {}
void ArchiveFile::parse() {
// Parse a MemoryBufferRef as an archive file.
// Read the symbol table to construct Lazy objects.
for (const Archive::Symbol &sym : file->symbols())
- symtab->addLazyArchive(this, sym);
+ ctx.symtab.addLazyArchive(this, sym);
}
// Returns a buffer pointing to a member file containing a given symbol.
void ArchiveFile::addMember(const Archive::Symbol &sym) {
const Archive::Child &c =
CHECK(sym.getMember(),
- "could not get the member for symbol " + toCOFFString(sym));
+ "could not get the member for symbol " + toCOFFString(ctx, sym));
// Return an empty buffer if we have already returned the same buffer.
if (!seen.insert(c.getChildOffset()).second)
return;
- driver->enqueueArchiveMember(c, sym, getName());
+ ctx.driver.enqueueArchiveMember(c, sym, getName());
}
std::vector<MemoryBufferRef> lld::coff::getArchiveMembers(Archive *file) {
return v;
}
-void LazyObjFile::fetch() {
- if (mb.getBuffer().empty())
- return;
-
- InputFile *file;
- if (isBitcode(mb))
- file = make<BitcodeFile>(mb, "", 0, std::move(symbols));
- else
- file = make<ObjFile>(mb, std::move(symbols));
- mb = {};
- symtab->addFile(file);
-}
-
-void LazyObjFile::parse() {
- if (isBitcode(this->mb)) {
- // Bitcode file.
- std::unique_ptr<lto::InputFile> obj =
- CHECK(lto::InputFile::create(this->mb), this);
- for (const lto::InputFile::Symbol &sym : obj->symbols()) {
- if (!sym.isUndefined())
- symtab->addLazyObject(this, sym.getName());
- }
- return;
- }
-
+void ObjFile::parseLazy() {
// Native object file.
std::unique_ptr<Binary> coffObjPtr = CHECK(createBinary(mb), this);
COFFObjectFile *coffObj = cast<COFFObjectFile>(coffObjPtr.get());
StringRef name = check(coffObj->getSymbolName(coffSym));
if (coffSym.isAbsolute() && ignoredSymbolName(name))
continue;
- symtab->addLazyObject(this, name);
+ ctx.symtab.addLazyObject(this, name);
i += coffSym.getNumberOfAuxSymbols();
}
}
// and then write it to a separate .pdb file.
// Ignore DWARF debug info unless /debug is given.
- if (!config->debug && name.startswith(".debug_"))
+ if (!ctx.config.debug && name.startswith(".debug_"))
return nullptr;
if (sec->Characteristics & llvm::COFF::IMAGE_SCN_LNK_REMOVE)
guardEHContChunks.push_back(c);
else if (name == ".sxdata")
sxDataChunks.push_back(c);
- else if (config->tailMerge && sec->NumberOfRelocations == 0 &&
+ else if (ctx.config.tailMerge && sec->NumberOfRelocations == 0 &&
name == ".rdata" && leaderName.startswith("??_C@"))
// COFF sections that look like string literal sections (i.e. no
// relocations, in .rdata, leader symbol name matches the MSVC name mangling
// for string literals) are subject to string tail merging.
- MergeChunk::addSection(c);
+ MergeChunk::addSection(ctx, c);
else if (name == ".rsrc" || name.startswith(".rsrc$"))
resourceChunks.push_back(c);
else
if (sym.isExternal()) {
StringRef name = check(coffObj->getSymbolName(sym));
if (sc)
- return symtab->addRegular(this, name, sym.getGeneric(), sc,
- sym.getValue());
+ return ctx.symtab.addRegular(this, name, sym.getGeneric(), sc,
+ sym.getValue());
// For MinGW symbols named .weak.* that point to a discarded section,
// don't create an Undefined symbol. If nothing ever refers to the symbol,
// everything should be fine. If something actually refers to the symbol
// (e.g. the undefined weak alias), linking will fail due to undefined
// references at the end.
- if (config->mingw && name.startswith(".weak."))
+ if (ctx.config.mingw && name.startswith(".weak."))
return nullptr;
- return symtab->addUndefined(name, this, false);
+ return ctx.symtab.addUndefined(name, this, false);
}
if (sc)
return make<DefinedRegular>(this, /*Name*/ "", /*IsCOMDAT*/ false,
symbols[i] = createUndefined(coffSym);
uint32_t tagIndex = coffSym.getAux<coff_aux_weak_external>()->TagIndex;
weakAliases.emplace_back(symbols[i], tagIndex);
- } else if (Optional<Symbol *> optSym =
+ } else if (std::optional<Symbol *> optSym =
createDefined(coffSym, comdatDefs, prevailingComdat)) {
symbols[i] = *optSym;
- if (config->mingw && prevailingComdat)
+ if (ctx.config.mingw && prevailingComdat)
recordPrevailingSymbolForMingw(coffSym, prevailingSectionMap);
} else {
- // createDefined() returns None if a symbol belongs to a section that
- // was pending at the point when the symbol was read. This can happen in
- // two cases:
+ // createDefined() returns std::nullopt if a symbol belongs to a section
+ // that was pending at the point when the symbol was read. This can happen
+ // in two cases:
// 1) section definition symbol for a comdat leader;
// 2) symbol belongs to a comdat section associated with another section.
// In both of these cases, we can expect the section to be resolved by
if (const coff_aux_section_definition *def = sym.getSectionDefinition()) {
if (def->Selection == IMAGE_COMDAT_SELECT_ASSOCIATIVE)
readAssociativeDefinition(sym, def);
- else if (config->mingw)
+ else if (ctx.config.mingw)
maybeAssociateSEHForMingw(sym, def, prevailingSectionMap);
}
if (sparseChunks[sym.getSectionNumber()] == pendingComdat) {
for (auto &kv : weakAliases) {
Symbol *sym = kv.first;
uint32_t idx = kv.second;
- checkAndSetWeakAlias(symtab, this, sym, symbols[idx]);
+ checkAndSetWeakAlias(ctx, this, sym, symbols[idx]);
}
// Free the memory used by sparseChunks now that symbol loading is finished.
Symbol *ObjFile::createUndefined(COFFSymbolRef sym) {
StringRef name = check(coffObj->getSymbolName(sym));
- return symtab->addUndefined(name, this, sym.isWeakExternal());
+ return ctx.symtab.addUndefined(name, this, sym.isWeakExternal());
}
static const coff_aux_section_definition *findSectionDef(COFFObjectFile *obj,
// Clang on the other hand picks "any". To be able to link two object files
// with a __declspec(selectany) declaration, one compiled with gcc and the
// other with clang, we merge them as proper "same size as"
- if (config->mingw && ((selection == IMAGE_COMDAT_SELECT_ANY &&
- leaderSelection == IMAGE_COMDAT_SELECT_SAME_SIZE) ||
- (selection == IMAGE_COMDAT_SELECT_SAME_SIZE &&
- leaderSelection == IMAGE_COMDAT_SELECT_ANY))) {
+ if (ctx.config.mingw && ((selection == IMAGE_COMDAT_SELECT_ANY &&
+ leaderSelection == IMAGE_COMDAT_SELECT_SAME_SIZE) ||
+ (selection == IMAGE_COMDAT_SELECT_SAME_SIZE &&
+ leaderSelection == IMAGE_COMDAT_SELECT_ANY))) {
leaderSelection = selection = IMAGE_COMDAT_SELECT_SAME_SIZE;
}
// seems better though.
// (This behavior matches ModuleLinker::getComdatResult().)
if (selection != leaderSelection) {
- log(("conflicting comdat type for " + toString(*leader) + ": " +
+ log(("conflicting comdat type for " + toString(ctx, *leader) + ": " +
Twine((int)leaderSelection) + " in " + toString(leader->getFile()) +
" and " + Twine((int)selection) + " in " + toString(this))
.str());
- symtab->reportDuplicate(leader, this);
+ ctx.symtab.reportDuplicate(leader, this);
return;
}
switch (selection) {
case IMAGE_COMDAT_SELECT_NODUPLICATES:
- symtab->reportDuplicate(leader, this);
+ ctx.symtab.reportDuplicate(leader, this);
break;
case IMAGE_COMDAT_SELECT_ANY:
case IMAGE_COMDAT_SELECT_SAME_SIZE:
if (leaderChunk->getSize() != getSection(sym)->SizeOfRawData) {
- if (!config->mingw) {
- symtab->reportDuplicate(leader, this);
+ if (!ctx.config.mingw) {
+ ctx.symtab.reportDuplicate(leader, this);
} else {
const coff_aux_section_definition *leaderDef = nullptr;
if (leaderChunk->file)
leaderDef = findSectionDef(leaderChunk->file->getCOFFObj(),
leaderChunk->getSectionNumber());
if (!leaderDef || leaderDef->Length != def->Length)
- symtab->reportDuplicate(leader, this);
+ ctx.symtab.reportDuplicate(leader, this);
}
}
break;
// if the two comdat sections have e.g. different alignment.
// Match that.
if (leaderChunk->getContents() != newChunk.getContents())
- symtab->reportDuplicate(leader, this, &newChunk, sym.getValue());
+ ctx.symtab.reportDuplicate(leader, this, &newChunk, sym.getValue());
break;
}
}
}
-Optional<Symbol *> ObjFile::createDefined(
+std::optional<Symbol *> ObjFile::createDefined(
COFFSymbolRef sym,
std::vector<const coff_aux_section_definition *> &comdatDefs,
bool &prevailing) {
if (sym.isCommon()) {
auto *c = make<CommonChunk>(sym);
chunks.push_back(c);
- return symtab->addCommon(this, getName(), sym.getValue(), sym.getGeneric(),
- c);
+ return ctx.symtab.addCommon(this, getName(), sym.getValue(),
+ sym.getGeneric(), c);
}
if (sym.isAbsolute()) {
return nullptr;
if (sym.isExternal())
- return symtab->addAbsolute(name, sym);
- return make<DefinedAbsolute>(name, sym);
+ return ctx.symtab.addAbsolute(name, sym);
+ return make<DefinedAbsolute>(ctx, name, sym);
}
int32_t sectionNumber = sym.getSectionNumber();
// The second symbol entry has the name of the comdat symbol, called the
// "comdat leader".
// When this function is called for the first symbol entry of a comdat,
- // it sets comdatDefs and returns None, and when it's called for the second
- // symbol entry it reads comdatDefs and then sets it back to nullptr.
+ // it sets comdatDefs and returns std::nullopt, and when it's called for the
+ // second symbol entry it reads comdatDefs and then sets it back to nullptr.
// Handle comdat leader.
if (const coff_aux_section_definition *def = comdatDefs[sectionNumber]) {
if (sym.isExternal()) {
std::tie(leader, prevailing) =
- symtab->addComdat(this, getName(), sym.getGeneric());
+ ctx.symtab.addComdat(this, getName(), sym.getGeneric());
} else {
leader = make<DefinedRegular>(this, /*Name*/ "", /*IsCOMDAT*/ false,
/*IsExternal*/ false, sym.getGeneric());
if (def->Selection != IMAGE_COMDAT_SELECT_ASSOCIATIVE)
comdatDefs[sectionNumber] = def;
}
- return None;
+ return std::nullopt;
}
return createRegular(sym);
if (sym->kind() == SymbolKind::S_OBJNAME) {
auto objName = cantFail(SymbolDeserializer::deserializeAs<ObjNameSym>(
sym.get()));
- pchSignature = objName.Signature;
+ if (objName.Signature)
+ pchSignature = objName.Signature;
}
offset += sym->length();
}
// DebugTypes.h). Both cases only happen with cl.exe: clang-cl produces regular
// output even with /Yc and /Yu and with /Zi.
void ObjFile::initializeDependencies() {
- if (!config->debug)
+ if (!ctx.config.debug)
return;
bool isPCH = false;
else
data = getDebugSection(".debug$T");
- // Don't make a TpiSource for objects with no debug info. If the object has
// symbols but no types, make a plain, empty TpiSource anyway, because it
// simplifies adding the symbols later.
if (data.empty()) {
if (!debugChunks.empty())
- debugTypesObj = makeTpiSource(this);
+ debugTypesObj = makeTpiSource(ctx, this);
return;
}
// This object file is a PCH file that others will depend on.
if (isPCH) {
- debugTypesObj = makePrecompSource(this);
+ debugTypesObj = makePrecompSource(ctx, this);
return;
}
if (firstType->kind() == LF_TYPESERVER2) {
TypeServer2Record ts = cantFail(
TypeDeserializer::deserializeAs<TypeServer2Record>(firstType->data()));
- debugTypesObj = makeUseTypeServerSource(this, ts);
- PDBInputFile::enqueue(ts.getName(), this);
+ debugTypesObj = makeUseTypeServerSource(ctx, this, ts);
+ enqueuePdbFile(ts.getName(), this);
return;
}
if (firstType->kind() == LF_PRECOMP) {
PrecompRecord precomp = cantFail(
TypeDeserializer::deserializeAs<PrecompRecord>(firstType->data()));
- debugTypesObj = makeUsePrecompSource(this, precomp);
+ // We're better off trusting the LF_PRECOMP signature. In some cases the
+ // S_OBJNAME record doesn't contain a valid PCH signature.
+ if (precomp.Signature)
+ pchSignature = precomp.Signature;
+ debugTypesObj = makeUsePrecompSource(ctx, this, precomp);
// Drop the LF_PRECOMP record from the input stream.
debugTypes = debugTypes.drop_front(firstType->RecordData.size());
return;
}
// This is a plain old object file.
- debugTypesObj = makeTpiSource(this);
+ debugTypesObj = makeTpiSource(ctx, this);
}
// Make a PDB path assuming the PDB is in the same folder as the OBJ
// The casing of the PDB path stamped in the OBJ can differ from the actual path
// on disk. With this, we ensure to always use lowercase as a key for the
-// PDBInputFile::instances map, at least on Windows.
+// pdbInputFileInstances map, at least on Windows.
static std::string normalizePdbPath(StringRef path) {
#if defined(_WIN32)
return path.lower();
}
// If existing, return the actual PDB path on disk.
-static Optional<std::string> findPdbPath(StringRef pdbPath,
- ObjFile *dependentFile) {
+static std::optional<std::string> findPdbPath(StringRef pdbPath,
+ ObjFile *dependentFile) {
// Ensure the file exists before anything else. In some cases, if the path
// points to a removable device, Driver::enqueuePath() would fail with an
// error (EAGAIN, "resource unavailable try again") which we want to skip
std::string ret = getPdbBaseName(dependentFile, pdbPath);
if (llvm::sys::fs::exists(ret))
return normalizePdbPath(ret);
- return None;
+ return std::nullopt;
}
-PDBInputFile::PDBInputFile(MemoryBufferRef m) : InputFile(PDBKind, m) {}
+PDBInputFile::PDBInputFile(COFFLinkerContext &ctx, MemoryBufferRef m)
+ : InputFile(ctx, PDBKind, m) {}
PDBInputFile::~PDBInputFile() = default;
-PDBInputFile *PDBInputFile::findFromRecordPath(StringRef path,
+PDBInputFile *PDBInputFile::findFromRecordPath(const COFFLinkerContext &ctx,
+ StringRef path,
ObjFile *fromFile) {
auto p = findPdbPath(path.str(), fromFile);
if (!p)
return nullptr;
- auto it = PDBInputFile::instances.find(*p);
- if (it != PDBInputFile::instances.end())
+ auto it = ctx.pdbInputFileInstances.find(*p);
+ if (it != ctx.pdbInputFileInstances.end())
return it->second;
return nullptr;
}
-void PDBInputFile::enqueue(StringRef path, ObjFile *fromFile) {
- auto p = findPdbPath(path.str(), fromFile);
- if (!p)
- return;
- auto it = PDBInputFile::instances.emplace(*p, nullptr);
- if (!it.second)
- return; // already scheduled for load
- driver->enqueuePDB(*p);
-}
-
void PDBInputFile::parse() {
- PDBInputFile::instances[mb.getBufferIdentifier().str()] = this;
+ ctx.pdbInputFileInstances[mb.getBufferIdentifier().str()] = this;
std::unique_ptr<pdb::IPDBSession> thisSession;
- loadErr.emplace(pdb::NativeSession::createFromPdb(
- MemoryBuffer::getMemBuffer(mb, false), thisSession));
- if (*loadErr)
+ Error E = pdb::NativeSession::createFromPdb(
+ MemoryBuffer::getMemBuffer(mb, false), thisSession);
+ if (E) {
+ loadErrorStr.emplace(toString(std::move(E)));
return; // fail silently at this point - the error will be handled later,
// when merging the debug type stream
+ }
session.reset(static_cast<pdb::NativeSession *>(thisSession.release()));
auto expectedInfo = pdbFile.getPDBInfoStream();
// All PDB Files should have an Info stream.
if (!expectedInfo) {
- loadErr.emplace(expectedInfo.takeError());
+ loadErrorStr.emplace(toString(expectedInfo.takeError()));
return;
}
- debugTypesObj = makeTypeServerSource(this);
+ debugTypesObj = makeTypeServerSource(ctx, this);
}
// Used only for DWARF debug info, which is not common (except in MinGW
// environments). This returns an optional pair of file name and line
// number for where the variable was defined.
-Optional<std::pair<StringRef, uint32_t>>
+std::optional<std::pair<StringRef, uint32_t>>
ObjFile::getVariableLocation(StringRef var) {
if (!dwarf) {
dwarf = make<DWARFCache>(DWARFContext::create(*getCOFFObj()));
if (!dwarf)
- return None;
+ return std::nullopt;
}
- if (config->machine == I386)
+ if (ctx.config.machine == I386)
var.consume_front("_");
- Optional<std::pair<std::string, unsigned>> ret = dwarf->getVariableLoc(var);
+ std::optional<std::pair<std::string, unsigned>> ret =
+ dwarf->getVariableLoc(var);
if (!ret)
- return None;
- return std::make_pair(saver.save(ret->first), ret->second);
+ return std::nullopt;
+ return std::make_pair(saver().save(ret->first), ret->second);
}
// Used only for DWARF debug info, which is not common (except in MinGW
// environments).
-Optional<DILineInfo> ObjFile::getDILineInfo(uint32_t offset,
- uint32_t sectionIndex) {
+std::optional<DILineInfo> ObjFile::getDILineInfo(uint32_t offset,
+ uint32_t sectionIndex) {
if (!dwarf) {
dwarf = make<DWARFCache>(DWARFContext::create(*getCOFFObj()));
if (!dwarf)
- return None;
+ return std::nullopt;
}
return dwarf->getDILineInfo(offset, sectionIndex);
}
+void ObjFile::enqueuePdbFile(StringRef path, ObjFile *fromFile) {
+ auto p = findPdbPath(path.str(), fromFile);
+ if (!p)
+ return;
+ auto it = ctx.pdbInputFileInstances.emplace(*p, nullptr);
+ if (!it.second)
+ return; // already scheduled for load
+ ctx.driver.enqueuePDB(*p);
+}
+
+ImportFile::ImportFile(COFFLinkerContext &ctx, MemoryBufferRef m)
+ : InputFile(ctx, ImportKind, m), live(!ctx.config.doGC), thunkLive(live) {}
+
void ImportFile::parse() {
const char *buf = mb.getBufferStart();
const auto *hdr = reinterpret_cast<const coff_import_header *>(buf);
fatal("broken import library");
// Read names and create an __imp_ symbol.
- StringRef name = saver.save(StringRef(buf + sizeof(*hdr)));
- StringRef impName = saver.save("__imp_" + name);
+ StringRef name = saver().save(StringRef(buf + sizeof(*hdr)));
+ StringRef impName = saver().save("__imp_" + name);
const char *nameStart = buf + sizeof(coff_import_header) + name.size() + 1;
dllName = std::string(StringRef(nameStart));
StringRef extName;
this->hdr = hdr;
externalName = extName;
- impSym = symtab->addImportData(impName, this);
+ impSym = ctx.symtab.addImportData(impName, this);
// If this was a duplicate, we logged an error but may continue;
// in this case, impSym is nullptr.
if (!impSym)
return;
if (hdr->getType() == llvm::COFF::IMPORT_CONST)
- static_cast<void>(symtab->addImportData(name, this));
+ static_cast<void>(ctx.symtab.addImportData(name, this));
// If type is function, we need to create a thunk which jump to an
// address pointed by the __imp_ symbol. (This allows you to call
// DLL functions just like regular non-DLL functions.)
if (hdr->getType() == llvm::COFF::IMPORT_CODE)
- thunkSym = symtab->addImportThunk(
+ thunkSym = ctx.symtab.addImportThunk(
name, cast_or_null<DefinedImportData>(impSym), hdr->Machine);
}
-BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
- uint64_t offsetInArchive)
- : BitcodeFile(mb, archiveName, offsetInArchive, {}) {}
-
-BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
- uint64_t offsetInArchive,
- std::vector<Symbol *> &&symbols)
- : InputFile(BitcodeKind, mb), symbols(std::move(symbols)) {
+BitcodeFile::BitcodeFile(COFFLinkerContext &ctx, MemoryBufferRef mb,
+ StringRef archiveName, uint64_t offsetInArchive,
+ bool lazy)
+ : InputFile(ctx, BitcodeKind, mb, lazy) {
std::string path = mb.getBufferIdentifier().str();
- if (config->thinLTOIndexOnly)
- path = replaceThinLTOSuffix(mb.getBufferIdentifier());
+ if (ctx.config.thinLTOIndexOnly)
+ path = replaceThinLTOSuffix(mb.getBufferIdentifier(),
+ ctx.config.thinLTOObjectSuffixReplace.first,
+ ctx.config.thinLTOObjectSuffixReplace.second);
// ThinLTO assumes that all MemoryBufferRefs given to it have a unique
// name. If two archives define two members with the same name, this
// into consideration at LTO time (which very likely causes undefined
// symbols later in the link stage). So we append file offset to make
// filename unique.
- MemoryBufferRef mbref(
- mb.getBuffer(),
- saver.save(archiveName.empty() ? path
- : archiveName + sys::path::filename(path) +
- utostr(offsetInArchive)));
+ MemoryBufferRef mbref(mb.getBuffer(),
+ saver().save(archiveName.empty()
+ ? path
+ : archiveName +
+ sys::path::filename(path) +
+ utostr(offsetInArchive)));
obj = check(lto::InputFile::create(mbref));
}
BitcodeFile::~BitcodeFile() = default;
-namespace {
-// Convenience class for initializing a coff_section with specific flags.
-class FakeSection {
-public:
- FakeSection(int c) { section.Characteristics = c; }
-
- coff_section section;
-};
-
-// Convenience class for initializing a SectionChunk with specific flags.
-class FakeSectionChunk {
-public:
- FakeSectionChunk(const coff_section *section) : chunk(nullptr, section) {
- // Comdats from LTO files can't be fully treated as regular comdats
- // at this point; we don't know what size or contents they are going to
- // have, so we can't do proper checking of such aspects of them.
- chunk.selection = IMAGE_COMDAT_SELECT_ANY;
- }
-
- SectionChunk chunk;
-};
-
-FakeSection ltoTextSection(IMAGE_SCN_MEM_EXECUTE);
-FakeSection ltoDataSection(IMAGE_SCN_CNT_INITIALIZED_DATA);
-FakeSectionChunk ltoTextSectionChunk(<oTextSection.section);
-FakeSectionChunk ltoDataSectionChunk(<oDataSection.section);
-} // namespace
-
void BitcodeFile::parse() {
+ llvm::StringSaver &saver = lld::saver();
+
std::vector<std::pair<Symbol *, bool>> comdat(obj->getComdatTable().size());
for (size_t i = 0; i != obj->getComdatTable().size(); ++i)
// FIXME: Check nodeduplicate
comdat[i] =
- symtab->addComdat(this, saver.save(obj->getComdatTable()[i].first));
+ ctx.symtab.addComdat(this, saver.save(obj->getComdatTable()[i].first));
for (const lto::InputFile::Symbol &objSym : obj->symbols()) {
StringRef symName = saver.save(objSym.getName());
int comdatIndex = objSym.getComdatIndex();
Symbol *sym;
SectionChunk *fakeSC = nullptr;
if (objSym.isExecutable())
- fakeSC = <oTextSectionChunk.chunk;
+ fakeSC = &ctx.ltoTextSectionChunk.chunk;
else
- fakeSC = <oDataSectionChunk.chunk;
+ fakeSC = &ctx.ltoDataSectionChunk.chunk;
if (objSym.isUndefined()) {
- sym = symtab->addUndefined(symName, this, false);
+ sym = ctx.symtab.addUndefined(symName, this, false);
} else if (objSym.isCommon()) {
- sym = symtab->addCommon(this, symName, objSym.getCommonSize());
+ sym = ctx.symtab.addCommon(this, symName, objSym.getCommonSize());
} else if (objSym.isWeak() && objSym.isIndirect()) {
// Weak external.
- sym = symtab->addUndefined(symName, this, true);
+ sym = ctx.symtab.addUndefined(symName, this, true);
std::string fallback = std::string(objSym.getCOFFWeakExternalFallback());
- Symbol *alias = symtab->addUndefined(saver.save(fallback));
- checkAndSetWeakAlias(symtab, this, sym, alias);
+ Symbol *alias = ctx.symtab.addUndefined(saver.save(fallback));
+ checkAndSetWeakAlias(ctx, this, sym, alias);
} else if (comdatIndex != -1) {
if (symName == obj->getComdatTable()[comdatIndex].first) {
sym = comdat[comdatIndex].first;
if (cast<DefinedRegular>(sym)->data == nullptr)
cast<DefinedRegular>(sym)->data = &fakeSC->repl;
} else if (comdat[comdatIndex].second) {
- sym = symtab->addRegular(this, symName, nullptr, fakeSC);
+ sym = ctx.symtab.addRegular(this, symName, nullptr, fakeSC);
} else {
- sym = symtab->addUndefined(symName, this, false);
+ sym = ctx.symtab.addUndefined(symName, this, false);
}
} else {
- sym = symtab->addRegular(this, symName, nullptr, fakeSC);
+ sym = ctx.symtab.addRegular(this, symName, nullptr, fakeSC, 0,
+ objSym.isWeak());
}
symbols.push_back(sym);
if (objSym.isUsed())
- config->gcroot.push_back(sym);
+ ctx.config.gcroot.push_back(sym);
}
directives = obj->getCOFFLinkerOpts();
}
+void BitcodeFile::parseLazy() {
+ for (const lto::InputFile::Symbol &sym : obj->symbols())
+ if (!sym.isUndefined())
+ ctx.symtab.addLazyObject(this, sym.getName());
+}
+
MachineTypes BitcodeFile::getMachineType() {
switch (Triple(obj->getTargetTriple()).getArch()) {
case Triple::x86_64:
}
}
-std::string lld::coff::replaceThinLTOSuffix(StringRef path) {
- StringRef suffix = config->thinLTOObjectSuffixReplace.first;
- StringRef repl = config->thinLTOObjectSuffixReplace.second;
-
+std::string lld::coff::replaceThinLTOSuffix(StringRef path, StringRef suffix,
+ StringRef repl) {
if (path.consume_back(suffix))
return (path + repl).str();
return std::string(path);
s->nameType = ImportNameType::IMPORT_NAME;
if (coffObj->getMachine() == I386) {
- s->symbolName = symbolName = saver.save("_" + symbolName);
+ s->symbolName = symbolName = saver().save("_" + symbolName);
s->nameType = ImportNameType::IMPORT_NAME_NOPREFIX;
}
- StringRef impName = saver.save("__imp_" + symbolName);
- symtab->addLazyDLLSymbol(this, s, impName);
+ StringRef impName = saver().save("__imp_" + symbolName);
+ ctx.symtab.addLazyDLLSymbol(this, s, impName);
if (code)
- symtab->addLazyDLLSymbol(this, s, symbolName);
+ ctx.symtab.addLazyDLLSymbol(this, s, symbolName);
}
}
size_t impSize = s->dllName.size() + s->symbolName.size() + 2; // +2 for NULs
size_t size = sizeof(coff_import_header) + impSize;
- char *buf = bAlloc.Allocate<char>(size);
+ char *buf = bAlloc().Allocate<char>(size);
memset(buf, 0, size);
char *p = buf;
auto *imp = reinterpret_cast<coff_import_header *>(p);
p += s->symbolName.size() + 1;
memcpy(p, s->dllName.data(), s->dllName.size());
MemoryBufferRef mbref = MemoryBufferRef(StringRef(buf, size), s->dllName);
- ImportFile *impFile = make<ImportFile>(mbref);
- symtab->addFile(impFile);
+ ImportFile *impFile = make<ImportFile>(ctx, mbref);
+ ctx.symtab.addFile(impFile);
}
class DWARFCache;
namespace coff {
+class COFFLinkerContext;
std::vector<MemoryBufferRef> getArchiveMembers(llvm::object::Archive *file);
// Returns .drectve section contents if exist.
StringRef getDirectives() { return directives; }
+ COFFLinkerContext &ctx;
+
protected:
- InputFile(Kind k, MemoryBufferRef m) : mb(m), fileKind(k) {}
+ InputFile(COFFLinkerContext &c, Kind k, MemoryBufferRef m, bool lazy = false)
+ : mb(m), ctx(c), fileKind(k), lazy(lazy) {}
StringRef directives;
private:
const Kind fileKind;
+
+public:
+ // True if this is a lazy ObjFile or BitcodeFile.
+ bool lazy = false;
};
// .lib or .a file.
class ArchiveFile : public InputFile {
public:
- explicit ArchiveFile(MemoryBufferRef m);
+ explicit ArchiveFile(COFFLinkerContext &ctx, MemoryBufferRef m);
static bool classof(const InputFile *f) { return f->kind() == ArchiveKind; }
void parse() override;
llvm::DenseSet<uint64_t> seen;
};
-// .obj or .o file between -start-lib and -end-lib.
-class LazyObjFile : public InputFile {
-public:
- explicit LazyObjFile(MemoryBufferRef m) : InputFile(LazyObjectKind, m) {}
- static bool classof(const InputFile *f) {
- return f->kind() == LazyObjectKind;
- }
- // Makes this object file part of the link.
- void fetch();
- // Adds the symbols in this file to the symbol table as LazyObject symbols.
- void parse() override;
-
-private:
- std::vector<Symbol *> symbols;
-};
-
// .obj or .o file. This may be a member of an archive file.
class ObjFile : public InputFile {
public:
- explicit ObjFile(MemoryBufferRef m) : InputFile(ObjectKind, m) {}
- explicit ObjFile(MemoryBufferRef m, std::vector<Symbol *> &&symbols)
- : InputFile(ObjectKind, m), symbols(std::move(symbols)) {}
+ explicit ObjFile(COFFLinkerContext &ctx, MemoryBufferRef m, bool lazy = false)
+ : InputFile(ctx, ObjectKind, m, lazy) {}
static bool classof(const InputFile *f) { return f->kind() == ObjectKind; }
void parse() override;
+ void parseLazy();
MachineTypes getMachineType() override;
ArrayRef<Chunk *> getChunks() { return chunks; }
ArrayRef<SectionChunk *> getDebugChunks() { return debugChunks; }
bool isResourceObjFile() const { return !resourceChunks.empty(); }
- static std::vector<ObjFile *> instances;
-
// Flags in the absolute @feat.00 symbol if it is present. These usually
// indicate if an object was compiled with certain security features enabled
// like stack guard, safeseh, /guard:cf, or other things.
// When using Microsoft precompiled headers, this is the PCH's key.
// The same key is used by both the precompiled object, and objects using the
// precompiled object. Any difference indicates out-of-date objects.
- llvm::Optional<uint32_t> pchSignature;
+ std::optional<uint32_t> pchSignature;
// Whether this file was compiled with /hotpatch.
bool hotPatchable = false;
// The .debug$P or .debug$T section data if present. Empty otherwise.
ArrayRef<uint8_t> debugTypes;
- llvm::Optional<std::pair<StringRef, uint32_t>>
+ std::optional<std::pair<StringRef, uint32_t>>
getVariableLocation(StringRef var);
- llvm::Optional<llvm::DILineInfo> getDILineInfo(uint32_t offset,
- uint32_t sectionIndex);
+ std::optional<llvm::DILineInfo> getDILineInfo(uint32_t offset,
+ uint32_t sectionIndex);
private:
const coff_section* getSection(uint32_t i);
return getSection(sym.getSectionNumber());
}
+ void enqueuePdbFile(StringRef path, ObjFile *fromFile);
+
void initializeChunks();
void initializeSymbols();
void initializeFlags();
bool &prevailing, DefinedRegular *leader,
const llvm::object::coff_aux_section_definition *def);
- llvm::Optional<Symbol *>
+ std::optional<Symbol *>
createDefined(COFFSymbolRef sym,
std::vector<const llvm::object::coff_aux_section_definition *>
&comdatDefs,
// stream.
class PDBInputFile : public InputFile {
public:
- explicit PDBInputFile(MemoryBufferRef m);
+ explicit PDBInputFile(COFFLinkerContext &ctx, MemoryBufferRef m);
~PDBInputFile();
static bool classof(const InputFile *f) { return f->kind() == PDBKind; }
void parse() override;
- static void enqueue(StringRef path, ObjFile *fromFile);
-
- static PDBInputFile *findFromRecordPath(StringRef path, ObjFile *fromFile);
-
- static std::map<std::string, PDBInputFile *> instances;
+ static PDBInputFile *findFromRecordPath(const COFFLinkerContext &ctx,
+ StringRef path, ObjFile *fromFile);
// Record possible errors while opening the PDB file
- llvm::Optional<Error> loadErr;
+ std::optional<std::string> loadErrorStr;
// This is the actual interface to the PDB (if it was opened successfully)
std::unique_ptr<llvm::pdb::NativeSession> session;
// for details about the format.
class ImportFile : public InputFile {
public:
- explicit ImportFile(MemoryBufferRef m) : InputFile(ImportKind, m) {}
+ explicit ImportFile(COFFLinkerContext &ctx, MemoryBufferRef m);
static bool classof(const InputFile *f) { return f->kind() == ImportKind; }
- static std::vector<ImportFile *> instances;
-
Symbol *impSym = nullptr;
Symbol *thunkSym = nullptr;
std::string dllName;
// symbols provided by this import library member. We also track whether the
// imported symbol is used separately from whether the thunk is used in order
// to avoid creating unnecessary thunks.
- bool live = !config->doGC;
- bool thunkLive = !config->doGC;
+ bool live;
+ bool thunkLive;
};
// Used for LTO.
class BitcodeFile : public InputFile {
public:
- BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
- uint64_t offsetInArchive);
- explicit BitcodeFile(MemoryBufferRef m, StringRef archiveName,
- uint64_t offsetInArchive,
- std::vector<Symbol *> &&symbols);
+ explicit BitcodeFile(COFFLinkerContext &ctx, MemoryBufferRef mb,
+ StringRef archiveName, uint64_t offsetInArchive,
+ bool lazy);
~BitcodeFile();
static bool classof(const InputFile *f) { return f->kind() == BitcodeKind; }
ArrayRef<Symbol *> getSymbols() { return symbols; }
MachineTypes getMachineType() override;
- static std::vector<BitcodeFile *> instances;
+ void parseLazy();
std::unique_ptr<llvm::lto::InputFile> obj;
private:
// .dll file. MinGW only.
class DLLFile : public InputFile {
public:
- explicit DLLFile(MemoryBufferRef m) : InputFile(DLLKind, m) {}
+ explicit DLLFile(COFFLinkerContext &ctx, MemoryBufferRef m)
+ : InputFile(ctx, DLLKind, m) {}
static bool classof(const InputFile *f) { return f->kind() == DLLKind; }
void parse() override;
MachineTypes getMachineType() override;
return identify_magic(mb.getBuffer()) == llvm::file_magic::bitcode;
}
-std::string replaceThinLTOSuffix(StringRef path);
+std::string replaceThinLTOSuffix(StringRef path, StringRef suffix,
+ StringRef repl);
} // namespace coff
std::string toString(const coff::InputFile *file);
//===----------------------------------------------------------------------===//
#include "LLDMapFile.h"
+#include "COFFLinkerContext.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "Writer.h"
}
// Returns a list of all symbols that we want to print out.
-static std::vector<DefinedRegular *> getSymbols() {
+static std::vector<DefinedRegular *> getSymbols(const COFFLinkerContext &ctx) {
std::vector<DefinedRegular *> v;
- for (ObjFile *file : ObjFile::instances)
+ for (ObjFile *file : ctx.objFileInstances)
for (Symbol *b : file->getSymbols())
if (auto *sym = dyn_cast_or_null<DefinedRegular>(b))
if (sym && !sym->getCOFFSymbol().isSectionDefinition())
// Construct a map from symbols to their stringified representations.
static DenseMap<DefinedRegular *, std::string>
-getSymbolStrings(ArrayRef<DefinedRegular *> syms) {
+getSymbolStrings(const COFFLinkerContext &ctx,
+ ArrayRef<DefinedRegular *> syms) {
std::vector<std::string> str(syms.size());
- parallelForEachN((size_t)0, syms.size(), [&](size_t i) {
+ parallelFor((size_t)0, syms.size(), [&](size_t i) {
raw_string_ostream os(str[i]);
writeHeader(os, syms[i]->getRVA(), 0, 0);
- os << indent16 << toString(*syms[i]);
+ os << indent16 << toString(ctx, *syms[i]);
});
DenseMap<DefinedRegular *, std::string> ret;
return ret;
}
-void lld::coff::writeLLDMapFile(ArrayRef<OutputSection *> outputSections) {
- if (config->lldmapFile.empty())
+void lld::coff::writeLLDMapFile(const COFFLinkerContext &ctx) {
+ if (ctx.config.lldmapFile.empty())
return;
std::error_code ec;
- raw_fd_ostream os(config->lldmapFile, ec, sys::fs::OF_None);
+ raw_fd_ostream os(ctx.config.lldmapFile, ec, sys::fs::OF_None);
if (ec)
- fatal("cannot open " + config->lldmapFile + ": " + ec.message());
+ fatal("cannot open " + ctx.config.lldmapFile + ": " + ec.message());
// Collect symbol info that we want to print out.
- std::vector<DefinedRegular *> syms = getSymbols();
+ std::vector<DefinedRegular *> syms = getSymbols(ctx);
SymbolMapTy sectionSyms = getSectionSyms(syms);
- DenseMap<DefinedRegular *, std::string> symStr = getSymbolStrings(syms);
+ DenseMap<DefinedRegular *, std::string> symStr = getSymbolStrings(ctx, syms);
// Print out the header line.
os << "Address Size Align Out In Symbol\n";
// Print out file contents.
- for (OutputSection *sec : outputSections) {
+ for (OutputSection *sec : ctx.outputSections) {
writeHeader(os, sec->getRVA(), sec->getVirtualSize(), /*align=*/pageSize);
os << sec->name << '\n';
#ifndef LLD_COFF_LLDMAPFILE_H
#define LLD_COFF_LLDMAPFILE_H
-#include "llvm/ADT/ArrayRef.h"
-
-namespace lld {
-namespace coff {
-class OutputSection;
-void writeLLDMapFile(llvm::ArrayRef<OutputSection *> outputSections);
-}
+namespace lld::coff {
+class COFFLinkerContext;
+void writeLLDMapFile(const COFFLinkerContext &ctx);
}
#endif
//===----------------------------------------------------------------------===//
#include "LTO.h"
+#include "COFFLinkerContext.h"
#include "Config.h"
#include "InputFiles.h"
#include "Symbols.h"
#include "lld/Common/Args.h"
-#include "lld/Common/ErrorHandler.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Strings.h"
#include "lld/Common/TargetOptionsCommandFlags.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/IR/DiagnosticPrinter.h"
-#include "llvm/LTO/Caching.h"
#include "llvm/LTO/Config.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Object/SymbolicFile.h"
+#include "llvm/Support/Caching.h"
#include "llvm/Support/CodeGen.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
return ret;
}
-static std::string getThinLTOOutputFile(StringRef path) {
+std::string BitcodeCompiler::getThinLTOOutputFile(StringRef path) {
return lto::getThinLTOOutputFile(
- std::string(path), std::string(config->thinLTOPrefixReplace.first),
- std::string(config->thinLTOPrefixReplace.second));
+ std::string(path), std::string(ctx.config.thinLTOPrefixReplace.first),
+ std::string(ctx.config.thinLTOPrefixReplace.second));
}
-static lto::Config createConfig() {
+lto::Config BitcodeCompiler::createConfig() {
lto::Config c;
c.Options = initTargetOptionsFromCodeGenFlags();
c.Options.EmitAddrsig = true;
+ for (StringRef C : ctx.config.mllvmOpts)
+ c.MllvmArgs.emplace_back(C.str());
// Always emit a section per function/datum with LTO. LLVM LTO should get most
// of the benefit of linker GC, but there are still opportunities for ICF.
// Use static reloc model on 32-bit x86 because it usually results in more
// compact code, and because there are also known code generation bugs when
// using the PIC model (see PR34306).
- if (config->machine == COFF::IMAGE_FILE_MACHINE_I386)
+ if (ctx.config.machine == COFF::IMAGE_FILE_MACHINE_I386)
c.RelocModel = Reloc::Static;
else
c.RelocModel = Reloc::PIC_;
+#ifndef NDEBUG
+ c.DisableVerify = false;
+#else
c.DisableVerify = true;
+#endif
c.DiagHandler = diagnosticHandler;
- c.OptLevel = config->ltoo;
+ c.OptLevel = ctx.config.ltoo;
c.CPU = getCPUStr();
c.MAttrs = getMAttrs();
- c.CGOptLevel = args::getCGOptLevel(config->ltoo);
- c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty();
- c.UseNewPM = config->ltoNewPassManager;
- c.DebugPassManager = config->ltoDebugPassManager;
- c.CSIRProfile = std::string(config->ltoCSProfileFile);
- c.RunCSIRInstr = config->ltoCSProfileGenerate;
-
- if (config->saveTemps)
- checkError(c.addSaveTemps(std::string(config->outputFile) + ".",
+ c.CGOptLevel = args::getCGOptLevel(ctx.config.ltoo);
+ c.AlwaysEmitRegularLTOObj = !ctx.config.ltoObjPath.empty();
+ c.DebugPassManager = ctx.config.ltoDebugPassManager;
+ c.CSIRProfile = std::string(ctx.config.ltoCSProfileFile);
+ c.RunCSIRInstr = ctx.config.ltoCSProfileGenerate;
+ c.PGOWarnMismatch = ctx.config.ltoPGOWarnMismatch;
+
+ if (ctx.config.saveTemps)
+ checkError(c.addSaveTemps(std::string(ctx.config.outputFile) + ".",
/*UseInputModulePath*/ true));
return c;
}
-BitcodeCompiler::BitcodeCompiler() {
+BitcodeCompiler::BitcodeCompiler(COFFLinkerContext &c) : ctx(c) {
// Initialize indexFile.
- if (!config->thinLTOIndexOnlyArg.empty())
- indexFile = openFile(config->thinLTOIndexOnlyArg);
+ if (!ctx.config.thinLTOIndexOnlyArg.empty())
+ indexFile = openFile(ctx.config.thinLTOIndexOnlyArg);
// Initialize ltoObj.
lto::ThinBackend backend;
- if (config->thinLTOIndexOnly) {
+ if (ctx.config.thinLTOIndexOnly) {
auto OnIndexWrite = [&](StringRef S) { thinIndices.erase(S); };
backend = lto::createWriteIndexesThinBackend(
- std::string(config->thinLTOPrefixReplace.first),
- std::string(config->thinLTOPrefixReplace.second),
- config->thinLTOEmitImportsFiles, indexFile.get(), OnIndexWrite);
+ std::string(ctx.config.thinLTOPrefixReplace.first),
+ std::string(ctx.config.thinLTOPrefixReplace.second),
+ ctx.config.thinLTOEmitImportsFiles, indexFile.get(), OnIndexWrite);
} else {
backend = lto::createInProcessThinBackend(
- llvm::heavyweight_hardware_concurrency(config->thinLTOJobs));
+ llvm::heavyweight_hardware_concurrency(ctx.config.thinLTOJobs));
}
ltoObj = std::make_unique<lto::LTO>(createConfig(), backend,
- config->ltoPartitions);
+ ctx.config.ltoPartitions);
}
BitcodeCompiler::~BitcodeCompiler() = default;
std::vector<Symbol *> symBodies = f.getSymbols();
std::vector<lto::SymbolResolution> resols(symBodies.size());
- if (config->thinLTOIndexOnly)
+ if (ctx.config.thinLTOIndexOnly)
thinIndices.insert(obj.getName());
// Provide a resolution to the LTO API for each symbol.
unsigned maxTasks = ltoObj->getMaxTasks();
buf.resize(maxTasks);
files.resize(maxTasks);
+ file_names.resize(maxTasks);
// The /lldltocache option specifies the path to a directory in which to cache
// native object files for ThinLTO incremental builds. If a path was
// specified, configure LTO to use it as the cache directory.
- lto::NativeObjectCache cache;
- if (!config->ltoCache.empty())
- cache = check(lto::localCache(
- config->ltoCache, [&](size_t task, std::unique_ptr<MemoryBuffer> mb) {
- files[task] = std::move(mb);
- }));
+ FileCache cache;
+ if (!ctx.config.ltoCache.empty())
+ cache = check(localCache("ThinLTO", "Thin", ctx.config.ltoCache,
+ [&](size_t task, const Twine &moduleName,
+ std::unique_ptr<MemoryBuffer> mb) {
+ files[task] = std::move(mb);
+ file_names[task] = moduleName.str();
+ }));
checkError(ltoObj->run(
- [&](size_t task) {
- return std::make_unique<lto::NativeObjectStream>(
- std::make_unique<raw_svector_ostream>(buf[task]));
+ [&](size_t task, const Twine &moduleName) {
+ buf[task].first = moduleName.str();
+ return std::make_unique<CachedFileStream>(
+ std::make_unique<raw_svector_ostream>(buf[task].second));
},
cache));
for (StringRef s : thinIndices) {
std::string path = getThinLTOOutputFile(s);
openFile(path + ".thinlto.bc");
- if (config->thinLTOEmitImportsFiles)
+ if (ctx.config.thinLTOEmitImportsFiles)
openFile(path + ".imports");
}
// ThinLTO with index only option is required to generate only the index
// files. After that, we exit from linker and ThinLTO backend runs in a
// distributed environment.
- if (config->thinLTOIndexOnly) {
- if (!config->ltoObjPath.empty())
- saveBuffer(buf[0], config->ltoObjPath);
+ if (ctx.config.thinLTOIndexOnly) {
+ if (!ctx.config.ltoObjPath.empty())
+ saveBuffer(buf[0].second, ctx.config.ltoObjPath);
if (indexFile)
indexFile->close();
return {};
}
- if (!config->ltoCache.empty())
- pruneCache(config->ltoCache, config->ltoCachePolicy);
+ if (!ctx.config.ltoCache.empty())
+ pruneCache(ctx.config.ltoCache, ctx.config.ltoCachePolicy, files);
std::vector<InputFile *> ret;
for (unsigned i = 0; i != maxTasks; ++i) {
- // Assign unique names to LTO objects. This ensures they have unique names
- // in the PDB if one is produced. The names should look like:
- // - foo.exe.lto.obj
- // - foo.exe.lto.1.obj
- // - ...
- StringRef ltoObjName =
- saver.save(Twine(config->outputFile) + ".lto" +
- (i == 0 ? Twine("") : Twine('.') + Twine(i)) + ".obj");
-
+ StringRef bitcodeFilePath;
// Get the native object contents either from the cache or from memory. Do
// not use the cached MemoryBuffer directly, or the PDB will not be
// deterministic.
StringRef objBuf;
- if (files[i])
+ if (files[i]) {
objBuf = files[i]->getBuffer();
- else
- objBuf = buf[i];
+ bitcodeFilePath = file_names[i];
+ } else {
+ objBuf = buf[i].second;
+ bitcodeFilePath = buf[i].first;
+ }
if (objBuf.empty())
continue;
- if (config->saveTemps)
- saveBuffer(buf[i], ltoObjName);
- ret.push_back(make<ObjFile>(MemoryBufferRef(objBuf, ltoObjName)));
+ // If the input bitcode file is path/to/a.obj, then the corresponding lto
+ // object file name will look something like: path/to/main.exe.lto.a.obj.
+ StringRef ltoObjName;
+ if (bitcodeFilePath == "ld-temp.o") {
+ ltoObjName =
+ saver().save(Twine(ctx.config.outputFile) + ".lto" +
+ (i == 0 ? Twine("") : Twine('.') + Twine(i)) + ".obj");
+ } else {
+ StringRef directory = sys::path::parent_path(bitcodeFilePath);
+ StringRef baseName = sys::path::filename(bitcodeFilePath);
+ StringRef outputFileBaseName = sys::path::filename(ctx.config.outputFile);
+ SmallString<64> path;
+ sys::path::append(path, directory,
+ outputFileBaseName + ".lto." + baseName);
+ sys::path::remove_dots(path, true);
+ ltoObjName = saver().save(path.str());
+ }
+ if (ctx.config.saveTemps)
+ saveBuffer(buf[i].second, ltoObjName);
+ ret.push_back(make<ObjFile>(ctx, MemoryBufferRef(objBuf, ltoObjName)));
}
return ret;
#include <memory>
#include <vector>
-namespace llvm {
-namespace lto {
+namespace llvm::lto {
+struct Config;
class LTO;
}
-}
-namespace lld {
-namespace coff {
+namespace lld::coff {
class BitcodeFile;
class InputFile;
+class COFFLinkerContext;
class BitcodeCompiler {
public:
- BitcodeCompiler();
+ BitcodeCompiler(COFFLinkerContext &ctx);
~BitcodeCompiler();
void add(BitcodeFile &f);
private:
std::unique_ptr<llvm::lto::LTO> ltoObj;
- std::vector<SmallString<0>> buf;
+ std::vector<std::pair<std::string, SmallString<0>>> buf;
std::vector<std::unique_ptr<MemoryBuffer>> files;
+ std::vector<std::string> file_names;
std::unique_ptr<llvm::raw_fd_ostream> indexFile;
llvm::DenseSet<StringRef> thinIndices;
+
+ std::string getThinLTOOutputFile(StringRef path);
+ llvm::lto::Config createConfig();
+
+ COFFLinkerContext &ctx;
};
}
-}
#endif
//===----------------------------------------------------------------------===//
#include "MapFile.h"
+#include "COFFLinkerContext.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "Writer.h"
using namespace lld;
using namespace lld::coff;
-static Timer totalMapTimer("MAP emission (Cumulative)", Timer::root());
-static Timer symbolGatherTimer("Gather symbols", totalMapTimer);
-static Timer symbolStringsTimer("Build symbol strings", totalMapTimer);
-static Timer writeTimer("Write to file", totalMapTimer);
-
// Print out the first two columns of a line.
static void writeHeader(raw_ostream &os, uint32_t sec, uint64_t addr) {
os << format(" %04x:%08llx", sec, addr);
time->tm_sec, time->tm_year + 1900);
}
-static void sortUniqueSymbols(std::vector<Defined *> &syms) {
+static void sortUniqueSymbols(std::vector<Defined *> &syms,
+ uint64_t imageBase) {
// Build helper vector
using SortEntry = std::pair<Defined *, size_t>;
std::vector<SortEntry> v;
v.erase(end, v.end());
// Sort by RVA then original order
- parallelSort(v, [](const SortEntry &a, const SortEntry &b) {
- // Add config->imageBase to avoid comparing "negative" RVAs.
+ parallelSort(v, [imageBase](const SortEntry &a, const SortEntry &b) {
+ // Add config.imageBase to avoid comparing "negative" RVAs.
// This can happen with symbols of Absolute kind
- uint64_t rvaa = config->imageBase + a.first->getRVA();
- uint64_t rvab = config->imageBase + b.first->getRVA();
+ uint64_t rvaa = imageBase + a.first->getRVA();
+ uint64_t rvab = imageBase + b.first->getRVA();
return rvaa < rvab || (rvaa == rvab && a.second < b.second);
});
}
// Returns the lists of all symbols that we want to print out.
-static void getSymbols(std::vector<Defined *> &syms,
+static void getSymbols(const COFFLinkerContext &ctx,
+ std::vector<Defined *> &syms,
std::vector<Defined *> &staticSyms) {
- for (ObjFile *file : ObjFile::instances)
+ for (ObjFile *file : ctx.objFileInstances)
for (Symbol *b : file->getSymbols()) {
if (!b || !b->isLive())
continue;
}
}
- for (ImportFile *file : ImportFile::instances) {
+ for (ImportFile *file : ctx.importFileInstances) {
if (!file->live)
continue;
syms.push_back(impSym);
}
- sortUniqueSymbols(syms);
- sortUniqueSymbols(staticSyms);
+ sortUniqueSymbols(syms, ctx.config.imageBase);
+ sortUniqueSymbols(staticSyms, ctx.config.imageBase);
}
// Construct a map from symbols to their stringified representations.
static DenseMap<Defined *, std::string>
-getSymbolStrings(ArrayRef<Defined *> syms) {
+getSymbolStrings(const COFFLinkerContext &ctx, ArrayRef<Defined *> syms) {
std::vector<std::string> str(syms.size());
- parallelForEachN((size_t)0, syms.size(), [&](size_t i) {
+ parallelFor((size_t)0, syms.size(), [&](size_t i) {
raw_string_ostream os(str[i]);
Defined *sym = syms[i];
fileDescr = "<common>";
} else if (Chunk *chunk = sym->getChunk()) {
address = sym->getRVA();
- if (OutputSection *sec = chunk->getOutputSection())
+ if (OutputSection *sec = ctx.getOutputSection(chunk))
address -= sec->header.VirtualAddress;
sectionIdx = chunk->getOutputSectionIdx();
os << " ";
os << left_justify(sym->getName(), 26);
os << " ";
- os << format_hex_no_prefix((config->imageBase + sym->getRVA()), 16);
+ os << format_hex_no_prefix((ctx.config.imageBase + sym->getRVA()), 16);
if (!fileDescr.empty()) {
os << " "; // FIXME : Handle "f" and "i" flags sometimes generated
// by link.exe in those spaces
return ret;
}
-void lld::coff::writeMapFile(ArrayRef<OutputSection *> outputSections) {
- if (config->mapFile.empty())
+void lld::coff::writeMapFile(COFFLinkerContext &ctx) {
+ if (ctx.config.mapFile.empty())
return;
std::error_code ec;
- raw_fd_ostream os(config->mapFile, ec, sys::fs::OF_None);
+ raw_fd_ostream os(ctx.config.mapFile, ec, sys::fs::OF_None);
if (ec)
- fatal("cannot open " + config->mapFile + ": " + ec.message());
+ fatal("cannot open " + ctx.config.mapFile + ": " + ec.message());
- ScopedTimer t1(totalMapTimer);
+ ScopedTimer t1(ctx.totalMapTimer);
// Collect symbol info that we want to print out.
- ScopedTimer t2(symbolGatherTimer);
+ ScopedTimer t2(ctx.symbolGatherTimer);
std::vector<Defined *> syms;
std::vector<Defined *> staticSyms;
- getSymbols(syms, staticSyms);
+ getSymbols(ctx, syms, staticSyms);
t2.stop();
- ScopedTimer t3(symbolStringsTimer);
- DenseMap<Defined *, std::string> symStr = getSymbolStrings(syms);
- DenseMap<Defined *, std::string> staticSymStr = getSymbolStrings(staticSyms);
+ ScopedTimer t3(ctx.symbolStringsTimer);
+ DenseMap<Defined *, std::string> symStr = getSymbolStrings(ctx, syms);
+ DenseMap<Defined *, std::string> staticSymStr =
+ getSymbolStrings(ctx, staticSyms);
t3.stop();
- ScopedTimer t4(writeTimer);
- SmallString<128> AppName = sys::path::filename(config->outputFile);
+ ScopedTimer t4(ctx.writeTimer);
+ SmallString<128> AppName = sys::path::filename(ctx.config.outputFile);
sys::path::replace_extension(AppName, "");
// Print out the file header
os << " " << AppName << "\n";
os << "\n";
- os << " Timestamp is " << format_hex_no_prefix(config->timestamp, 8) << " (";
- if (config->repro) {
+ os << " Timestamp is " << format_hex_no_prefix(ctx.config.timestamp, 8)
+ << " (";
+ if (ctx.config.repro) {
os << "Repro mode";
} else {
- writeFormattedTimestamp(os, config->timestamp);
+ writeFormattedTimestamp(os, ctx.config.timestamp);
}
os << ")\n";
os << "\n";
os << " Preferred load address is "
- << format_hex_no_prefix(config->imageBase, 16) << "\n";
+ << format_hex_no_prefix(ctx.config.imageBase, 16) << "\n";
os << "\n";
// Print out section table.
os << " Start Length Name Class\n";
- for (OutputSection *sec : outputSections) {
+ for (OutputSection *sec : ctx.outputSections) {
// Merge display of chunks with same sectionName
std::vector<std::pair<SectionChunk *, SectionChunk *>> ChunkRanges;
for (Chunk *c : sec->chunks) {
uint16_t entrySecIndex = 0;
uint64_t entryAddress = 0;
- if (!config->noEntry) {
- Defined *entry = dyn_cast_or_null<Defined>(config->entry);
+ if (!ctx.config.noEntry) {
+ Defined *entry = dyn_cast_or_null<Defined>(ctx.config.entry);
if (entry) {
Chunk *chunk = entry->getChunk();
entrySecIndex = chunk->getOutputSectionIdx();
entryAddress =
- entry->getRVA() - chunk->getOutputSection()->header.VirtualAddress;
+ entry->getRVA() - ctx.getOutputSection(chunk)->header.VirtualAddress;
}
}
os << " entry point at ";
for (Defined *sym : staticSyms)
os << staticSymStr[sym] << '\n';
+ // Print out the exported functions
+ if (ctx.config.mapInfo) {
+ os << "\n";
+ os << " Exports\n";
+ os << "\n";
+ os << " ordinal name\n\n";
+ for (Export &e : ctx.config.exports) {
+ os << format(" %7d", e.ordinal) << " " << e.name << "\n";
+ if (!e.extName.empty() && e.extName != e.name)
+ os << " exported name: " << e.extName << "\n";
+ }
+ }
+
t4.stop();
t1.stop();
}
#ifndef LLD_COFF_MAPFILE_H
#define LLD_COFF_MAPFILE_H
-#include "llvm/ADT/ArrayRef.h"
-
-namespace lld {
-namespace coff {
-class OutputSection;
-void writeMapFile(llvm::ArrayRef<OutputSection *> outputSections);
-}
+namespace lld::coff {
+class COFFLinkerContext;
+void writeMapFile(COFFLinkerContext &ctx);
}
#endif
//
//===----------------------------------------------------------------------===//
+#include "COFFLinkerContext.h"
#include "Chunks.h"
#include "Symbols.h"
#include "lld/Common/Timer.h"
#include "llvm/ADT/STLExtras.h"
#include <vector>
-namespace lld {
-namespace coff {
-
-static Timer gctimer("GC", Timer::root());
+namespace lld::coff {
// Set live bit on for each reachable chunk. Unmarked (unreachable)
// COMDAT chunks will be ignored by Writer, so they will be excluded
// from the final output.
-void markLive(ArrayRef<Chunk *> chunks) {
- ScopedTimer t(gctimer);
+void markLive(COFFLinkerContext &ctx) {
+ ScopedTimer t(ctx.gcTimer);
// We build up a worklist of sections which have been marked as live. We only
// push into the worklist when we discover an unmarked section, and we mark
// COMDAT section chunks are dead by default. Add non-COMDAT chunks. Do not
// traverse DWARF sections. They are live, but they should not keep other
// sections alive.
- for (Chunk *c : chunks)
+ for (Chunk *c : ctx.symtab.getChunks())
if (auto *sc = dyn_cast<SectionChunk>(c))
if (sc->live && !sc->isDWARF())
worklist.push_back(sc);
};
// Add GC root chunks.
- for (Symbol *b : config->gcroot)
+ for (Symbol *b : ctx.config.gcroot)
addSym(b);
while (!worklist.empty()) {
enqueue(&c);
}
}
-
-}
}
#define LLD_COFF_MARKLIVE_H
#include "lld/Common/LLVM.h"
-#include "llvm/ADT/ArrayRef.h"
-namespace lld {
-namespace coff {
+namespace lld::coff {
-class Chunk;
+class COFFLinkerContext;
-void markLive(ArrayRef<Chunk *> chunks);
+void markLive(COFFLinkerContext &ctx);
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif // LLD_COFF_MARKLIVE_H
//===----------------------------------------------------------------------===//
#include "MinGW.h"
+#include "COFFLinkerContext.h"
#include "Driver.h"
#include "InputFiles.h"
#include "SymbolTable.h"
-#include "lld/Common/ErrorHandler.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/Object/COFF.h"
using namespace lld;
using namespace lld::coff;
-AutoExporter::AutoExporter() {
+AutoExporter::AutoExporter(
+ COFFLinkerContext &ctx,
+ const llvm::DenseSet<StringRef> &manualExcludeSymbols)
+ : manualExcludeSymbols(manualExcludeSymbols), ctx(ctx) {
excludeLibs = {
"libgcc",
"libgcc_s",
"libclang_rt.profile-x86_64",
"libc++",
"libc++abi",
+ "libFortran_main",
+ "libFortranRuntime",
+ "libFortranDecimal",
"libunwind",
"libmsvcrt",
"libucrtbase",
"_NULL_THUNK_DATA",
};
- if (config->machine == I386) {
+ if (ctx.config.machine == I386) {
excludeSymbols = {
"__NULL_IMPORT_DESCRIPTOR",
"__pei386_runtime_relocator",
excludeLibs.erase(libName);
}
+void AutoExporter::addExcludedSymbol(StringRef symbol) {
+ excludeSymbols.insert(symbol);
+}
+
bool AutoExporter::shouldExport(Defined *sym) const {
if (!sym || !sym->getChunk())
return false;
// disallow import symbols.
if (!isa<DefinedRegular>(sym) && !isa<DefinedCommon>(sym))
return false;
- if (excludeSymbols.count(sym->getName()))
+ if (excludeSymbols.count(sym->getName()) || manualExcludeSymbols.count(sym->getName()))
return false;
for (StringRef prefix : excludeSymbolPrefixes.keys())
return false;
// If a corresponding __imp_ symbol exists and is defined, don't export it.
- if (symtab->find(("__imp_" + sym->getName()).str()))
+ if (ctx.symtab.find(("__imp_" + sym->getName()).str()))
return false;
// Check that file is non-null before dereferencing it, symbols not
return !excludeObjects.count(fileName);
}
-void lld::coff::writeDefFile(StringRef name) {
+void lld::coff::writeDefFile(StringRef name,
+ const std::vector<Export> &exports) {
std::error_code ec;
raw_fd_ostream os(name, ec, sys::fs::OF_None);
if (ec)
fatal("cannot open " + name + ": " + ec.message());
os << "EXPORTS\n";
- for (Export &e : config->exports) {
+ for (const Export &e : exports) {
os << " " << e.exportName << " "
<< "@" << e.ordinal;
if (auto *def = dyn_cast_or_null<Defined>(e.sym)) {
}
}
-static StringRef mangle(Twine sym) {
- assert(config->machine != IMAGE_FILE_MACHINE_UNKNOWN);
- if (config->machine == I386)
- return saver.save("_" + sym);
- return saver.save(sym);
+static StringRef mangle(Twine sym, MachineTypes machine) {
+ assert(machine != IMAGE_FILE_MACHINE_UNKNOWN);
+ if (machine == I386)
+ return saver().save("_" + sym);
+ return saver().save(sym);
}
// Handles -wrap option.
// like they are not being used at all, so we explicitly set some flags so
// that LTO won't eliminate them.
std::vector<WrappedSymbol>
-lld::coff::addWrappedSymbols(opt::InputArgList &args) {
+lld::coff::addWrappedSymbols(COFFLinkerContext &ctx, opt::InputArgList &args) {
std::vector<WrappedSymbol> v;
DenseSet<StringRef> seen;
if (!seen.insert(name).second)
continue;
- Symbol *sym = symtab->findUnderscore(name);
+ Symbol *sym = ctx.symtab.findUnderscore(name);
if (!sym)
continue;
- Symbol *real = symtab->addUndefined(mangle("__real_" + name));
- Symbol *wrap = symtab->addUndefined(mangle("__wrap_" + name));
+ Symbol *real =
+ ctx.symtab.addUndefined(mangle("__real_" + name, ctx.config.machine));
+ Symbol *wrap =
+ ctx.symtab.addUndefined(mangle("__wrap_" + name, ctx.config.machine));
v.push_back({sym, real, wrap});
// These symbols may seem undefined initially, but don't bail out
- // at symtab->reportUnresolvable() due to them, but let wrapSymbols
+ // at symtab.reportUnresolvable() due to them, but let wrapSymbols
// below sort things out before checking finally with
- // symtab->resolveRemainingUndefines().
+ // symtab.resolveRemainingUndefines().
sym->deferUndefined = true;
real->deferUndefined = true;
// We want to tell LTO not to inline symbols to be overwritten
// When this function is executed, only InputFiles and symbol table
// contain pointers to symbol objects. We visit them to replace pointers,
// so that wrapped symbols are swapped as instructed by the command line.
-void lld::coff::wrapSymbols(ArrayRef<WrappedSymbol> wrapped) {
+void lld::coff::wrapSymbols(COFFLinkerContext &ctx,
+ ArrayRef<WrappedSymbol> wrapped) {
DenseMap<Symbol *, Symbol *> map;
for (const WrappedSymbol &w : wrapped) {
map[w.sym] = w.wrap;
map[w.real] = w.sym;
if (Defined *d = dyn_cast<Defined>(w.wrap)) {
- Symbol *imp = symtab->find(("__imp_" + w.sym->getName()).str());
+ Symbol *imp = ctx.symtab.find(("__imp_" + w.sym->getName()).str());
// Create a new defined local import for the wrap symbol. If
// no imp prefixed symbol existed, there's no need for it.
// (We can't easily distinguish whether any object file actually
// referenced it or not, though.)
if (imp) {
DefinedLocalImport *wrapimp = make<DefinedLocalImport>(
- saver.save("__imp_" + w.wrap->getName()), d);
- symtab->localImportChunks.push_back(wrapimp->getChunk());
+ ctx, saver().save("__imp_" + w.wrap->getName()), d);
+ ctx.symtab.localImportChunks.push_back(wrapimp->getChunk());
map[imp] = wrapimp;
}
}
}
// Update pointers in input files.
- parallelForEach(ObjFile::instances, [&](ObjFile *file) {
+ parallelForEach(ctx.objFileInstances, [&](ObjFile *file) {
MutableArrayRef<Symbol *> syms = file->getMutableSymbols();
for (size_t i = 0, e = syms.size(); i != e; ++i)
if (Symbol *s = map.lookup(syms[i]))
#include "Symbols.h"
#include "lld/Common/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Option/ArgList.h"
#include <vector>
-namespace lld {
-namespace coff {
+namespace lld::coff {
+class COFFLinkerContext;
// Logic for deciding what symbols to export, when exporting all
// symbols for MinGW.
class AutoExporter {
public:
- AutoExporter();
+ AutoExporter(COFFLinkerContext &ctx,
+ const llvm::DenseSet<StringRef> &manualExcludeSymbols);
void addWholeArchive(StringRef path);
+ void addExcludedSymbol(StringRef symbol);
llvm::StringSet<> excludeSymbols;
llvm::StringSet<> excludeSymbolPrefixes;
llvm::StringSet<> excludeLibs;
llvm::StringSet<> excludeObjects;
+ const llvm::DenseSet<StringRef> &manualExcludeSymbols;
+
bool shouldExport(Defined *sym) const;
+
+private:
+ COFFLinkerContext &ctx;
};
-void writeDefFile(StringRef name);
+void writeDefFile(StringRef name, const std::vector<Export> &exports);
// The -wrap option is a feature to rename symbols so that you can write
// wrappers for existing functions. If you pass `-wrap:foo`, all
Symbol *wrap;
};
-std::vector<WrappedSymbol> addWrappedSymbols(llvm::opt::InputArgList &args);
+std::vector<WrappedSymbol> addWrappedSymbols(COFFLinkerContext &ctx,
+ llvm::opt::InputArgList &args);
-void wrapSymbols(ArrayRef<WrappedSymbol> wrapped);
+void wrapSymbols(COFFLinkerContext &ctx, ArrayRef<WrappedSymbol> wrapped);
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
MetaVarName<"[auto,always,never]">;
def defaultlib : P<"defaultlib", "Add the library to the list of input files">;
def delayload : P<"delayload", "Delay loaded DLL name">;
+def diasdkdir : P<"diasdkdir", "Set the location of the DIA SDK">;
def entry : P<"entry", "Name of entry point symbol">;
def errorlimit : P<"errorlimit",
"Maximum number of errors to emit before stopping (0 = no limit)">;
+def exclude_symbols : P<"exclude-symbols", "Exclude symbols from automatic export">,
+ MetaVarName<"<symbol[,symbol,...]>">;
def export : P<"export", "Export a function">;
// No help text because /failifmismatch is not intended to be used by the user.
def failifmismatch : P<"failifmismatch", "">;
def heap : P<"heap", "Size of the heap">;
def ignore : P<"ignore", "Specify warning codes to ignore">;
def implib : P<"implib", "Import library name">;
+def noimplib : F<"noimplib">,
+ HelpText<"Don't output an import lib">;
def lib : F<"lib">,
HelpText<"Act like lib.exe; must be first argument if present">;
def libpath : P<"libpath", "Additional library search path">;
def out : P<"out", "Path to file to write output">;
def natvis : P<"natvis", "Path to natvis file to embed in the PDB">;
def pdb : P<"pdb", "PDB file path">;
-def pdbstripped : P<"pdbstripped", "Stripped PDB file path">;
def pdbaltpath : P<"pdbaltpath", "PDB file path to embed in the image">;
+def pdbpagesize : P<"pdbpagesize", "PDB page size">;
+def pdbstripped : P<"pdbstripped", "Stripped PDB file path">;
def pdbstream : Joined<["/", "-", "/?", "-?"], "pdbstream:">,
MetaVarName<"<name>=<file>">,
HelpText<"Embed the contents of <file> in the PDB as named stream <name>">;
def stub : P<"stub", "Specify DOS stub file">;
def subsystem : P<"subsystem", "Specify subsystem">;
def timestamp : P<"timestamp", "Specify the PE header timestamp">;
+def vctoolsdir : P<"vctoolsdir", "Set the location of the VC tools">;
+def vctoolsversion : P<"vctoolsversion",
+ "Specify which VC tools version to use">;
def version : P<"version", "Specify a version number in the PE header">;
def wholearchive_file : P<"wholearchive",
"Include all object files from this library">;
+def winsdkdir : P<"winsdkdir", "Set the location of the Windows SDK">;
+def winsdkversion : P<"winsdkversion", "Specify which SDK version to use">;
+def winsysroot : P<"winsysroot",
+ "Adds several subdirectories to the library search paths">;
def disallowlib : Joined<["/", "-", "/?", "-?"], "disallowlib:">,
Alias<nodefaultlib>;
def verbose : F<"verbose">;
def wholearchive_flag : F<"wholearchive">,
HelpText<"Include all object files from all libraries">;
+def release : F<"release">,
+ HelpText<"Set the Checksum in the header of an PE file">;
def force : F<"force">,
HelpText<"Allow undefined and multiply defined symbols">;
HelpText<"Allow multiply defined symbols when creating executables">;
def force_multipleres : F<"force:multipleres">,
HelpText<"Allow multiply defined resources when creating executables">;
-defm WX : B<"WX", "Treat warnings as errors", "Don't treat warnings as errors">;
+defm WX : B<"WX", "Treat warnings as errors",
+ "Don't treat warnings as errors (default)">;
defm allowbind : B<"allowbind", "Enable DLL binding (default)",
"Disable DLL binding">;
defm incremental : B<"incremental",
"Keep original import library if contents are unchanged",
"Overwrite import library even if contents are unchanged">;
+defm inferasanlibs : B<"inferasanlibs",
+ "Unused, generates a warning",
+ "No effect (default)">;
defm integritycheck : B<"integritycheck",
"Set FORCE_INTEGRITY bit in PE header",
"No effect (default)">;
HelpText<"Perform context sensitive PGO instrumentation">;
def lto_cs_profile_file : P<"lto-cs-profile-file",
"Context sensitive profile file path">;
+defm lto_pgo_warn_mismatch: B<
+ "lto-pgo-warn-mismatch",
+ "turn on warnings about profile cfg mismatch (default)>",
+ "turn off warnings about profile cfg mismatch">;
def dash_dash_version : Flag<["--"], "version">,
HelpText<"Display the version number and exit">;
def threads
: P<"threads", "Number of threads. '1' disables multi-threading. By "
"default all available hardware threads are used">;
def call_graph_ordering_file: P<
- "call-graph-ordering-file",
+ "call-graph-ordering-file",
"Layout sections to optimize the given callgraph">;
defm call_graph_profile_sort: B<
"call-graph-profile-sort",
"/call-graph-profile-sort into the specified file">;
def wrap : P_priv<"wrap">;
+def vfsoverlay : P<"vfsoverlay", "Path to a vfsoverlay yaml file to optionally look for /defaultlib's in">;
+
// Flags for debugging
def lldmap : F<"lldmap">;
-def lldmap_file : Joined<["/", "-", "/?", "-?"], "lldmap:">;
+def lldmap_file : P_priv<"lldmap">;
def map : F<"map">;
-def map_file : Joined<["/", "-", "/?", "-?"], "map:">;
+def map_file : P_priv<"map">;
+def map_info : P<"mapinfo", "Include the specified information in a map file">;
def show_timing : F<"time">;
def summary : F<"summary">;
// The flags below do nothing. They are defined only for link.exe compatibility.
//==============================================================================
-class QF<string name> : Joined<["/", "-", "/?", "-?"], name#":">;
-
def ignoreidl : F<"ignoreidl">;
+def ltcg : F<"ltcg">;
+def assemblydebug : F<"assemblydebug">;
def nologo : F<"nologo">;
def throwingnew : F<"throwingnew">;
def editandcontinue : F<"editandcontinue">;
def fastfail : F<"fastfail">;
+def kernel : F<"kernel">;
+def pdbcompress : F<"pdbcompress">;
+def emitpogophaseinfo : F<"emitpogophaseinfo">;
-def delay : QF<"delay">;
-def errorreport : QF<"errorreport">;
-def idlout : QF<"idlout">;
-def maxilksize : QF<"maxilksize">;
-def tlbid : QF<"tlbid">;
-def tlbout : QF<"tlbout">;
-def verbose_all : QF<"verbose">;
-def guardsym : QF<"guardsym">;
+def delay : P_priv<"delay">;
+def errorreport : P_priv<"errorreport">;
+def idlout : P_priv<"idlout">;
+def ilk : P_priv<"ilk">;
+def ltcg_opt : P_priv<"ltcg">;
+def assemblydebug_opt : P_priv<"assemblydebug">;
+def ltcgout : P_priv<"ltcgout">;
+def maxilksize : P_priv<"maxilksize">;
+def tlbid : P_priv<"tlbid">;
+def tlbout : P_priv<"tlbout">;
+def verbose_all : P_priv<"verbose">;
+def guardsym : P_priv<"guardsym">;
//===----------------------------------------------------------------------===//
#include "PDB.h"
+#include "COFFLinkerContext.h"
#include "Chunks.h"
#include "Config.h"
#include "DebugTypes.h"
#include "Symbols.h"
#include "TypeMerger.h"
#include "Writer.h"
-#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Timer.h"
#include "llvm/DebugInfo/CodeView/DebugFrameDataSubsection.h"
+#include "llvm/DebugInfo/CodeView/DebugInlineeLinesSubsection.h"
+#include "llvm/DebugInfo/CodeView/DebugLinesSubsection.h"
#include "llvm/DebugInfo/CodeView/DebugSubsectionRecord.h"
#include "llvm/DebugInfo/CodeView/GlobalTypeTableBuilder.h"
#include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ScopedPrinter.h"
#include <memory>
+#include <optional>
using namespace llvm;
using namespace llvm::codeview;
using llvm::object::coff_section;
using llvm::pdb::StringTableFixup;
-static ExitOnError exitOnErr;
-
-static Timer totalPdbLinkTimer("PDB Emission (Cumulative)", Timer::root());
-static Timer addObjectsTimer("Add Objects", totalPdbLinkTimer);
-Timer lld::coff::loadGHashTimer("Global Type Hashing", addObjectsTimer);
-Timer lld::coff::mergeGHashTimer("GHash Type Merging", addObjectsTimer);
-static Timer typeMergingTimer("Type Merging", addObjectsTimer);
-static Timer symbolMergingTimer("Symbol Merging", addObjectsTimer);
-static Timer publicsLayoutTimer("Publics Stream Layout", totalPdbLinkTimer);
-static Timer tpiStreamLayoutTimer("TPI Stream Layout", totalPdbLinkTimer);
-static Timer diskCommitTimer("Commit to Disk", totalPdbLinkTimer);
-
namespace {
class DebugSHandler;
friend DebugSHandler;
public:
- PDBLinker(SymbolTable *symtab)
- : symtab(symtab), builder(bAlloc), tMerger(bAlloc) {
+ PDBLinker(COFFLinkerContext &ctx)
+ : builder(bAlloc()), tMerger(ctx, bAlloc()), ctx(ctx) {
// This isn't strictly necessary, but link.exe usually puts an empty string
// as the first "valid" string in the string table, so we do the same in
// order to maintain as much byte-for-byte compatibility as possible.
void addPublicsToPDB();
/// Link info for each import file in the symbol table into the PDB.
- void addImportFilesToPDB(ArrayRef<OutputSection *> outputSections);
+ void addImportFilesToPDB();
void createModuleDBI(ObjFile *file);
std::vector<StringTableFixup> &stringTableFixups,
BinaryStreamRef symData);
- // Write all module symbols from all all live debug symbol subsections of the
+ // Write all module symbols from all live debug symbol subsections of the
// given object file into the given stream writer.
Error writeAllModuleSymbolRecords(ObjFile *file, BinaryStreamWriter &writer);
std::vector<uint8_t> &storage);
/// Add the section map and section contributions to the PDB.
- void addSections(ArrayRef<OutputSection *> outputSections,
- ArrayRef<uint8_t> sectionTable);
+ void addSections(ArrayRef<uint8_t> sectionTable);
/// Write the PDB to disk and store the Guid generated for it in *Guid.
void commit(codeview::GUID *guid);
void printStats();
private:
- SymbolTable *symtab;
+ void pdbMakeAbsolute(SmallVectorImpl<char> &fileName);
+ void translateIdSymbols(MutableArrayRef<uint8_t> &recordData,
+ TpiSource *source);
+ void addCommonLinkerModuleSymbols(StringRef path,
+ pdb::DbiModuleDescriptorBuilder &mod);
pdb::PDBFileBuilder builder;
TypeMerger tMerger;
+ COFFLinkerContext &ctx;
+
/// PDBs use a single global string table for filenames in the file checksum
/// table.
DebugStringTableSubsection pdbStrTab;
// Visual Studio's debugger requires absolute paths in various places in the
// PDB to work without additional configuration:
// https://docs.microsoft.com/en-us/visualstudio/debugger/debug-source-files-common-properties-solution-property-pages-dialog-box
-static void pdbMakeAbsolute(SmallVectorImpl<char> &fileName) {
+void PDBLinker::pdbMakeAbsolute(SmallVectorImpl<char> &fileName) {
// The default behavior is to produce paths that are valid within the context
// of the machine that you perform the link on. If the linker is running on
// a POSIX system, we will output absolute POSIX paths. If the linker is
// It's not absolute in any path syntax. Relative paths necessarily refer to
// the local file system, so we can make it native without ending up with a
// nonsensical path.
- if (config->pdbSourcePath.empty()) {
+ if (ctx.config.pdbSourcePath.empty()) {
sys::path::native(fileName);
sys::fs::make_absolute(fileName);
+ sys::path::remove_dots(fileName, true);
return;
}
// Since PDB's are more of a Windows thing, we make this conservative and only
// decide that it's a unix path if we're fairly certain. Specifically, if
// it starts with a forward slash.
- SmallString<128> absoluteFileName = config->pdbSourcePath;
+ SmallString<128> absoluteFileName = ctx.config.pdbSourcePath;
sys::path::Style guessedStyle = absoluteFileName.startswith("/")
? sys::path::Style::posix
: sys::path::Style::windows;
});
}
-static void addGHashTypeInfo(pdb::PDBFileBuilder &builder) {
+static void addGHashTypeInfo(COFFLinkerContext &ctx,
+ pdb::PDBFileBuilder &builder) {
// Start the TPI or IPI stream header.
builder.getTpiBuilder().setVersionHeader(pdb::PdbTpiV80);
builder.getIpiBuilder().setVersionHeader(pdb::PdbTpiV80);
- for_each(TpiSource::instances, [&](TpiSource *source) {
+ for (TpiSource *source : ctx.tpiSourceList) {
builder.getTpiBuilder().addTypeRecords(source->mergedTpi.recs,
source->mergedTpi.recSizes,
source->mergedTpi.recHashes);
builder.getIpiBuilder().addTypeRecords(source->mergedIpi.recs,
source->mergedIpi.recSizes,
source->mergedIpi.recHashes);
- });
+ }
}
static void
}
/// MSVC translates S_PROC_ID_END to S_END, and S_[LG]PROC32_ID to S_[LG]PROC32
-static void translateIdSymbols(MutableArrayRef<uint8_t> &recordData,
- TypeMerger &tMerger, TpiSource *source) {
+void PDBLinker::translateIdSymbols(MutableArrayRef<uint8_t> &recordData,
+ TpiSource *source) {
RecordPrefix *prefix = reinterpret_cast<RecordPrefix *>(recordData.data());
SymbolKind kind = symbolKind(recordData);
// in both cases we just need the second type index.
if (!ti->isSimple() && !ti->isNoneType()) {
TypeIndex newType = TypeIndex(SimpleTypeKind::NotTranslated);
- if (config->debugGHashes) {
+ if (ctx.config.debugGHashes) {
auto idToType = tMerger.funcIdToType.find(*ti);
if (idToType != tMerger.funcIdToType.end())
newType = idToType->second;
unsigned symbolScopeDepth) {
switch (sym.kind()) {
case SymbolKind::S_GDATA32:
- case SymbolKind::S_CONSTANT:
case SymbolKind::S_GTHREAD32:
// We really should not be seeing S_PROCREF and S_LPROCREF in the first place
// since they are synthesized by the linker in response to S_GPROC32 and
case SymbolKind::S_PROCREF:
case SymbolKind::S_LPROCREF:
return false;
- // S_UDT records go in the module stream if it is not a global S_UDT.
+ // S_UDT and S_CONSTANT records go in the module stream if it is not a global record.
case SymbolKind::S_UDT:
+ case SymbolKind::S_CONSTANT:
return symbolScopeDepth > 0;
// S_GDATA32 does not go in the module stream, but S_LDATA32 does.
case SymbolKind::S_LDATA32:
static bool symbolGoesInGlobalsStream(const CVSymbol &sym,
unsigned symbolScopeDepth) {
switch (sym.kind()) {
- case SymbolKind::S_CONSTANT:
case SymbolKind::S_GDATA32:
case SymbolKind::S_GTHREAD32:
case SymbolKind::S_GPROC32:
case SymbolKind::S_UDT:
case SymbolKind::S_LDATA32:
case SymbolKind::S_LTHREAD32:
+ case SymbolKind::S_CONSTANT:
return symbolScopeDepth == 0;
default:
return false;
static void addGlobalSymbol(pdb::GSIStreamBuilder &builder, uint16_t modIndex,
unsigned symOffset,
std::vector<uint8_t> &symStorage) {
- CVSymbol sym(makeArrayRef(symStorage));
+ CVSymbol sym{ArrayRef(symStorage)};
switch (sym.kind()) {
case SymbolKind::S_CONSTANT:
case SymbolKind::S_UDT:
case SymbolKind::S_LPROCREF: {
// sym is a temporary object, so we have to copy and reallocate the record
// to stabilize it.
- uint8_t *mem = bAlloc.Allocate<uint8_t>(sym.length());
+ uint8_t *mem = bAlloc().Allocate<uint8_t>(sym.length());
memcpy(mem, sym.data().data(), sym.length());
- builder.addGlobalSymbol(CVSymbol(makeArrayRef(mem, sym.length())));
+ builder.addGlobalSymbol(CVSymbol(ArrayRef(mem, sym.length())));
break;
}
case SymbolKind::S_GPROC32:
// An object file may have S_xxx_ID symbols, but these get converted to
// "real" symbols in a PDB.
- translateIdSymbols(recordBytes, tMerger, source);
+ translateIdSymbols(recordBytes, source);
}
void PDBLinker::analyzeSymbolSubsection(
Error PDBLinker::writeAllModuleSymbolRecords(ObjFile *file,
BinaryStreamWriter &writer) {
+ ExitOnError exitOnErr;
std::vector<uint8_t> storage;
SmallVector<uint32_t, 4> scopes;
static_cast<ObjFile *>(obj), writer);
}
-static pdb::SectionContrib createSectionContrib(const Chunk *c, uint32_t modi) {
- OutputSection *os = c ? c->getOutputSection() : nullptr;
+static pdb::SectionContrib createSectionContrib(COFFLinkerContext &ctx,
+ const Chunk *c, uint32_t modi) {
+ OutputSection *os = c ? ctx.getOutputSection(c) : nullptr;
pdb::SectionContrib sc;
memset(&sc, 0, sizeof(sc));
sc.ISect = os ? os->sectionIndex : llvm::pdb::kInvalidStreamIndex;
contents = SectionChunk::consumeDebugMagic(contents, ".debug$S");
DebugSubsectionArray subsections;
BinaryStreamReader reader(contents, support::little);
+ ExitOnError exitOnErr;
exitOnErr(reader.readArray(subsections, contents.size()));
debugChunk->sortRelocations();
// Unclear what this is for.
break;
+ case DebugSubsectionKind::XfgHashType:
+ case DebugSubsectionKind::XfgHashVirtual:
+ break;
+
default:
warn("ignoring unknown debug$S subsection kind 0x" +
utohexstr(uint32_t(ss.kind())) + " in file " + toString(&file));
TpiSource *source = debugChunk->file->debugTypesObj;
DebugInlineeLinesSubsectionRef inlineeLines;
BinaryStreamReader storageReader(relocatedBytes, support::little);
+ ExitOnError exitOnErr;
exitOnErr(inlineeLines.initialize(storageReader));
for (const InlineeSourceLine &line : inlineeLines) {
TypeIndex &inlinee = *const_cast<TypeIndex *>(&line.Header->Inlinee);
return;
}
+ ExitOnError exitOnErr;
+
// Handle FPO data. Each subsection begins with a single image base
// relocation, which is then added to the RvaStart of each frame data record
// when it is added to the PDB. The string table indices for the FPO program
// size as the original. Otherwise, the file references in the line and
// inlinee line tables will be incorrect.
auto newChecksums = std::make_unique<DebugChecksumsSubsection>(linker.pdbStrTab);
- for (FileChecksumEntry &fc : checksums) {
+ for (const FileChecksumEntry &fc : checksums) {
SmallString<128> filename =
exitOnErr(cvStrTab.getString(fc.FileNameOffset));
- pdbMakeAbsolute(filename);
+ linker.pdbMakeAbsolute(filename);
exitOnErr(dbiBuilder.addModuleSourceFile(*file.moduleDBI, filename));
newChecksums->addChecksum(filename, fc.Kind, fc.Checksum);
}
file.moduleDBI->addDebugSubsection(std::move(newChecksums));
}
-static void warnUnusable(InputFile *f, Error e) {
- if (!config->warnDebugInfoUnusable) {
+static void warnUnusable(InputFile *f, Error e, bool shouldWarn) {
+ if (!shouldWarn) {
consumeError(std::move(e));
return;
}
// Allocate memory for a .debug$S / .debug$F section and relocate it.
static ArrayRef<uint8_t> relocateDebugChunk(SectionChunk &debugChunk) {
- uint8_t *buffer = bAlloc.Allocate<uint8_t>(debugChunk.getSize());
+ uint8_t *buffer = bAlloc().Allocate<uint8_t>(debugChunk.getSize());
assert(debugChunk.getOutputSectionIdx() == 0 &&
"debug sections should not be in output sections");
debugChunk.writeTo(buffer);
- return makeArrayRef(buffer, debugChunk.getSize());
+ return ArrayRef(buffer, debugChunk.getSize());
}
void PDBLinker::addDebugSymbols(TpiSource *source) {
if (!source->file)
return;
- ScopedTimer t(symbolMergingTimer);
+ ScopedTimer t(ctx.symbolMergingTimer);
+ ExitOnError exitOnErr;
pdb::DbiStreamBuilder &dbiBuilder = builder.getDbiBuilder();
DebugSHandler dsh(*this, *source->file, source);
// Now do all live .debug$S and .debug$F sections.
void PDBLinker::createModuleDBI(ObjFile *file) {
pdb::DbiStreamBuilder &dbiBuilder = builder.getDbiBuilder();
SmallString<128> objName;
+ ExitOnError exitOnErr;
bool inArchive = !file->parentName.empty();
objName = inArchive ? file->parentName : file->getName();
auto *secChunk = dyn_cast<SectionChunk>(c);
if (!secChunk || !secChunk->live)
continue;
- pdb::SectionContrib sc = createSectionContrib(secChunk, modi);
+ pdb::SectionContrib sc = createSectionContrib(ctx, secChunk, modi);
file->moduleDBI->setFirstSectionContrib(sc);
break;
}
// the PDB first, so that we can get the map from object file type and item
// indices to PDB type and item indices. If we are using ghashes, types have
// already been merged.
- if (!config->debugGHashes) {
- ScopedTimer t(typeMergingTimer);
+ if (!ctx.config.debugGHashes) {
+ ScopedTimer t(ctx.typeMergingTimer);
if (Error e = source->mergeDebugT(&tMerger)) {
// If type merging failed, ignore the symbols.
- warnUnusable(source->file, std::move(e));
+ warnUnusable(source->file, std::move(e),
+ ctx.config.warnDebugInfoUnusable);
return;
}
}
// If type merging failed, ignore the symbols.
Error typeError = std::move(source->typeMergingError);
if (typeError) {
- warnUnusable(source->file, std::move(typeError));
+ warnUnusable(source->file, std::move(typeError),
+ ctx.config.warnDebugInfoUnusable);
return;
}
addDebugSymbols(source);
}
-static pdb::BulkPublic createPublic(Defined *def) {
+static pdb::BulkPublic createPublic(COFFLinkerContext &ctx, Defined *def) {
pdb::BulkPublic pub;
pub.Name = def->getName().data();
pub.NameLen = def->getName().size();
}
pub.setFlags(flags);
- OutputSection *os = def->getChunk()->getOutputSection();
+ OutputSection *os = ctx.getOutputSection(def->getChunk());
assert(os && "all publics should be in final image");
pub.Offset = def->getRVA() - os->getRVA();
pub.Segment = os->sectionIndex;
// Add all object files to the PDB. Merge .debug$T sections into IpiData and
// TpiData.
void PDBLinker::addObjectsToPDB() {
- ScopedTimer t1(addObjectsTimer);
+ ScopedTimer t1(ctx.addObjectsTimer);
// Create module descriptors
- for_each(ObjFile::instances, [&](ObjFile *obj) { createModuleDBI(obj); });
+ for (ObjFile *obj : ctx.objFileInstances)
+ createModuleDBI(obj);
// Reorder dependency type sources to come first.
- TpiSource::sortDependencies();
+ tMerger.sortDependencies();
// Merge type information from input files using global type hashing.
- if (config->debugGHashes)
+ if (ctx.config.debugGHashes)
tMerger.mergeTypesWithGHash();
// Merge dependencies and then regular objects.
- for_each(TpiSource::dependencySources,
- [&](TpiSource *source) { addDebug(source); });
- for_each(TpiSource::objectSources,
- [&](TpiSource *source) { addDebug(source); });
+ for (TpiSource *source : tMerger.dependencySources)
+ addDebug(source);
+ for (TpiSource *source : tMerger.objectSources)
+ addDebug(source);
builder.getStringTableBuilder().setStrings(pdbStrTab);
t1.stop();
// Construct TPI and IPI stream contents.
- ScopedTimer t2(tpiStreamLayoutTimer);
+ ScopedTimer t2(ctx.tpiStreamLayoutTimer);
+
// Collect all the merged types.
- if (config->debugGHashes) {
- addGHashTypeInfo(builder);
+ if (ctx.config.debugGHashes) {
+ addGHashTypeInfo(ctx, builder);
} else {
addTypeInfo(builder.getTpiBuilder(), tMerger.getTypeTable());
addTypeInfo(builder.getIpiBuilder(), tMerger.getIDTable());
}
t2.stop();
- if (config->showSummary) {
- for_each(TpiSource::instances, [&](TpiSource *source) {
+ if (ctx.config.showSummary) {
+ for (TpiSource *source : ctx.tpiSourceList) {
nbTypeRecords += source->nbTypeRecords;
nbTypeRecordsBytes += source->nbTypeRecordsBytes;
- });
+ }
}
}
void PDBLinker::addPublicsToPDB() {
- ScopedTimer t3(publicsLayoutTimer);
+ ScopedTimer t3(ctx.publicsLayoutTimer);
// Compute the public symbols.
auto &gsiBuilder = builder.getGsiBuilder();
std::vector<pdb::BulkPublic> publics;
- symtab->forEachSymbol([&publics](Symbol *s) {
+ ctx.symtab.forEachSymbol([&publics, this](Symbol *s) {
// Only emit external, defined, live symbols that have a chunk. Static,
// non-external symbols do not appear in the symbol table.
auto *def = dyn_cast<Defined>(s);
StringRef name = def->getName();
if (name.data()[0] == '_' && name.data()[1] == '_') {
// Drop the '_' prefix for x86.
- if (config->machine == I386)
+ if (ctx.config.machine == I386)
name = name.drop_front(1);
if (name.startswith("__profd_") || name.startswith("__profc_") ||
name.startswith("__covrec_")) {
return;
}
}
- publics.push_back(createPublic(def));
+ publics.push_back(createPublic(ctx, def));
}
});
}
void PDBLinker::printStats() {
- if (!config->showSummary)
+ if (!ctx.config.showSummary)
return;
SmallString<256> buffer;
stream << format_decimal(v, 15) << " " << s << '\n';
};
- print(ObjFile::instances.size(),
+ print(ctx.objFileInstances.size(),
"Input OBJ files (expanded from all cmd-line inputs)");
- print(TpiSource::countTypeServerPDBs(), "PDB type server dependencies");
- print(TpiSource::countPrecompObjs(), "Precomp OBJ dependencies");
+ print(ctx.typeServerSourceMappings.size(), "PDB type server dependencies");
+ print(ctx.precompSourceMappings.size(), "Precomp OBJ dependencies");
print(nbTypeRecords, "Input type records");
print(nbTypeRecordsBytes, "Input type records bytes");
print(builder.getTpiBuilder().getRecordCount(), "Merged TPI records");
<< "Run llvm-pdbutil to print details about a particular record:\n";
stream << formatv("llvm-pdbutil dump -{0}s -{0}-index {1:X} {2}\n",
(name == "TPI" ? "type" : "id"),
- tsis.back().typeIndex.getIndex(), config->pdbPath);
+ tsis.back().typeIndex.getIndex(), ctx.config.pdbPath);
}
};
- if (!config->debugGHashes) {
+ if (!ctx.config.debugGHashes) {
// FIXME: Reimplement for ghash.
printLargeInputTypeRecs("TPI", tMerger.tpiCounts, tMerger.getTypeTable());
printLargeInputTypeRecs("IPI", tMerger.ipiCounts, tMerger.getIDTable());
}
void PDBLinker::addNatvisFiles() {
- for (StringRef file : config->natvisFiles) {
+ for (StringRef file : ctx.config.natvisFiles) {
ErrorOr<std::unique_ptr<MemoryBuffer>> dataOrErr =
MemoryBuffer::getFile(file);
if (!dataOrErr) {
std::unique_ptr<MemoryBuffer> data = std::move(*dataOrErr);
// Can't use takeBuffer() here since addInjectedSource() takes ownership.
- if (driver->tar)
- driver->tar->append(relativeToRoot(data->getBufferIdentifier()),
- data->getBuffer());
+ if (ctx.driver.tar)
+ ctx.driver.tar->append(relativeToRoot(data->getBufferIdentifier()),
+ data->getBuffer());
builder.addInjectedSource(file, std::move(data));
}
}
void PDBLinker::addNamedStreams() {
- for (const auto &streamFile : config->namedStreams) {
+ ExitOnError exitOnErr;
+ for (const auto &streamFile : ctx.config.namedStreams) {
const StringRef stream = streamFile.getKey(), file = streamFile.getValue();
ErrorOr<std::unique_ptr<MemoryBuffer>> dataOrErr =
MemoryBuffer::getFile(file);
}
std::unique_ptr<MemoryBuffer> data = std::move(*dataOrErr);
exitOnErr(builder.addNamedStream(stream, data->getBuffer()));
- driver->takeBuffer(std::move(data));
+ ctx.driver.takeBuffer(std::move(data));
}
}
for (StringRef a : args) {
if (!r.empty())
r.push_back(' ');
- bool hasWS = a.find(' ') != StringRef::npos;
- bool hasQ = a.find('"') != StringRef::npos;
+ bool hasWS = a.contains(' ');
+ bool hasQ = a.contains('"');
if (hasWS || hasQ)
r.push_back('"');
if (hasQ) {
return r;
}
-static void fillLinkerVerRecord(Compile3Sym &cs) {
- cs.Machine = toCodeViewMachine(config->machine);
+static void fillLinkerVerRecord(Compile3Sym &cs, MachineTypes machine) {
+ cs.Machine = toCodeViewMachine(machine);
// Interestingly, if we set the string to 0.0.0.0, then when trying to view
// local variables WinDbg emits an error that private symbols are not present.
// By setting this to a valid MSVC linker version string, local variables are
cs.setLanguage(SourceLanguage::Link);
}
-static void addCommonLinkerModuleSymbols(StringRef path,
- pdb::DbiModuleDescriptorBuilder &mod) {
+void PDBLinker::addCommonLinkerModuleSymbols(
+ StringRef path, pdb::DbiModuleDescriptorBuilder &mod) {
ObjNameSym ons(SymbolRecordKind::ObjNameSym);
EnvBlockSym ebs(SymbolRecordKind::EnvBlockSym);
Compile3Sym cs(SymbolRecordKind::Compile3Sym);
- fillLinkerVerRecord(cs);
+ fillLinkerVerRecord(cs, ctx.config.machine);
ons.Name = "* Linker *";
ons.Signature = 0;
- ArrayRef<StringRef> args = makeArrayRef(config->argv).drop_front();
+ ArrayRef<StringRef> args = ArrayRef(ctx.config.argv).drop_front();
std::string argStr = quote(args);
ebs.Fields.push_back("cwd");
SmallString<64> cwd;
- if (config->pdbSourcePath.empty())
+ if (ctx.config.pdbSourcePath.empty())
sys::fs::current_path(cwd);
else
- cwd = config->pdbSourcePath;
+ cwd = ctx.config.pdbSourcePath;
ebs.Fields.push_back(cwd);
ebs.Fields.push_back("exe");
- SmallString<64> exe = config->argv[0];
+ SmallString<64> exe = ctx.config.argv[0];
pdbMakeAbsolute(exe);
ebs.Fields.push_back(exe);
ebs.Fields.push_back("pdb");
ebs.Fields.push_back(path);
ebs.Fields.push_back("cmd");
ebs.Fields.push_back(argStr);
+ llvm::BumpPtrAllocator &bAlloc = lld::bAlloc();
mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol(
ons, bAlloc, CodeViewContainer::Pdb));
mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol(
cgs.Characteristics |= llvm::COFF::IMAGE_SCN_MEM_WRITE;
mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol(
- cgs, bAlloc, CodeViewContainer::Pdb));
+ cgs, bAlloc(), CodeViewContainer::Pdb));
}
static void addLinkerModuleSectionSymbol(pdb::DbiModuleDescriptorBuilder &mod,
- OutputSection &os) {
+ OutputSection &os, bool isMinGW) {
SectionSym sym(SymbolRecordKind::SectionSym);
sym.Alignment = 12; // 2^12 = 4KB
sym.Characteristics = os.header.Characteristics;
sym.Rva = os.getRVA();
sym.SectionNumber = os.sectionIndex;
mod.addSymbol(codeview::SymbolSerializer::writeOneSymbol(
- sym, bAlloc, CodeViewContainer::Pdb));
+ sym, bAlloc(), CodeViewContainer::Pdb));
// Skip COFF groups in MinGW because it adds a significant footprint to the
// PDB, due to each function being in its own section
- if (config->mingw)
+ if (isMinGW)
return;
// Output COFF groups for individual chunks of this section.
}
// Add all import files as modules to the PDB.
-void PDBLinker::addImportFilesToPDB(ArrayRef<OutputSection *> outputSections) {
- if (ImportFile::instances.empty())
+void PDBLinker::addImportFilesToPDB() {
+ if (ctx.importFileInstances.empty())
return;
+ ExitOnError exitOnErr;
std::map<std::string, llvm::pdb::DbiModuleDescriptorBuilder *> dllToModuleDbi;
- for (ImportFile *file : ImportFile::instances) {
+ for (ImportFile *file : ctx.importFileInstances) {
if (!file->live)
continue;
exitOnErr(dbiBuilder.addModuleInfo(file->dllName));
firstMod.setObjFileName(libPath);
pdb::SectionContrib sc =
- createSectionContrib(nullptr, llvm::pdb::kInvalidStreamIndex);
+ createSectionContrib(ctx, nullptr, llvm::pdb::kInvalidStreamIndex);
firstMod.setFirstSectionContrib(sc);
// The second module is where the import stream goes.
DefinedImportThunk *thunk = cast<DefinedImportThunk>(file->thunkSym);
Chunk *thunkChunk = thunk->getChunk();
- OutputSection *thunkOS = thunkChunk->getOutputSection();
+ OutputSection *thunkOS = ctx.getOutputSection(thunkChunk);
ObjNameSym ons(SymbolRecordKind::ObjNameSym);
Compile3Sym cs(SymbolRecordKind::Compile3Sym);
ons.Name = file->dllName;
ons.Signature = 0;
- fillLinkerVerRecord(cs);
+ fillLinkerVerRecord(cs, ctx.config.machine);
ts.Name = thunk->getName();
ts.Parent = 0;
ts.Segment = thunkOS->sectionIndex;
ts.Offset = thunkChunk->getRVA() - thunkOS->getRVA();
+ llvm::BumpPtrAllocator &bAlloc = lld::bAlloc();
mod->addSymbol(codeview::SymbolSerializer::writeOneSymbol(
ons, bAlloc, CodeViewContainer::Pdb));
mod->addSymbol(codeview::SymbolSerializer::writeOneSymbol(
mod->addSymbol(newSym);
pdb::SectionContrib sc =
- createSectionContrib(thunk->getChunk(), mod->getModuleIndex());
+ createSectionContrib(ctx, thunk->getChunk(), mod->getModuleIndex());
mod->setFirstSectionContrib(sc);
}
}
// Creates a PDB file.
-void lld::coff::createPDB(SymbolTable *symtab,
- ArrayRef<OutputSection *> outputSections,
+void lld::coff::createPDB(COFFLinkerContext &ctx,
ArrayRef<uint8_t> sectionTable,
llvm::codeview::DebugInfo *buildId) {
- ScopedTimer t1(totalPdbLinkTimer);
- PDBLinker pdb(symtab);
+ ScopedTimer t1(ctx.totalPdbLinkTimer);
+ PDBLinker pdb(ctx);
pdb.initialize(buildId);
pdb.addObjectsToPDB();
- pdb.addImportFilesToPDB(outputSections);
- pdb.addSections(outputSections, sectionTable);
+ pdb.addImportFilesToPDB();
+ pdb.addSections(sectionTable);
pdb.addNatvisFiles();
pdb.addNamedStreams();
pdb.addPublicsToPDB();
- ScopedTimer t2(diskCommitTimer);
+ ScopedTimer t2(ctx.diskCommitTimer);
codeview::GUID guid;
pdb.commit(&guid);
memcpy(&buildId->PDB70.Signature, &guid, 16);
}
void PDBLinker::initialize(llvm::codeview::DebugInfo *buildId) {
- exitOnErr(builder.initialize(4096)); // 4096 is blocksize
+ ExitOnError exitOnErr;
+ exitOnErr(builder.initialize(ctx.config.pdbPageSize));
buildId->Signature.CVSignature = OMF::Signature::PDB70;
// Signature is set to a hash of the PDB contents when the PDB is done.
pdb::DbiStreamBuilder &dbiBuilder = builder.getDbiBuilder();
dbiBuilder.setAge(buildId->PDB70.Age);
dbiBuilder.setVersionHeader(pdb::PdbDbiV70);
- dbiBuilder.setMachineType(config->machine);
+ dbiBuilder.setMachineType(ctx.config.machine);
// Technically we are not link.exe 14.11, but there are known cases where
// debugging tools on Windows expect Microsoft-specific version numbers or
// they fail to work at all. Since we know we produce PDBs that are
dbiBuilder.setBuildNumber(14, 11);
}
-void PDBLinker::addSections(ArrayRef<OutputSection *> outputSections,
- ArrayRef<uint8_t> sectionTable) {
+void PDBLinker::addSections(ArrayRef<uint8_t> sectionTable) {
+ ExitOnError exitOnErr;
// It's not entirely clear what this is, but the * Linker * module uses it.
pdb::DbiStreamBuilder &dbiBuilder = builder.getDbiBuilder();
- nativePath = config->pdbPath;
+ nativePath = ctx.config.pdbPath;
pdbMakeAbsolute(nativePath);
uint32_t pdbFilePathNI = dbiBuilder.addECName(nativePath);
auto &linkerModule = exitOnErr(dbiBuilder.addModuleInfo("* Linker *"));
addCommonLinkerModuleSymbols(nativePath, linkerModule);
// Add section contributions. They must be ordered by ascending RVA.
- for (OutputSection *os : outputSections) {
- addLinkerModuleSectionSymbol(linkerModule, *os);
+ for (OutputSection *os : ctx.outputSections) {
+ addLinkerModuleSectionSymbol(linkerModule, *os, ctx.config.mingw);
for (Chunk *c : os->chunks) {
pdb::SectionContrib sc =
- createSectionContrib(c, linkerModule.getModuleIndex());
+ createSectionContrib(ctx, c, linkerModule.getModuleIndex());
builder.getDbiBuilder().addSectionContrib(sc);
}
}
// to provide trampolines thunks for incremental function patching. Set this
// as "unused" because LLD doesn't support /INCREMENTAL link.
pdb::SectionContrib sc =
- createSectionContrib(nullptr, llvm::pdb::kInvalidStreamIndex);
+ createSectionContrib(ctx, nullptr, llvm::pdb::kInvalidStreamIndex);
linkerModule.setFirstSectionContrib(sc);
// Add Section Map stream.
// Print an error and continue if PDB writing fails. This is done mainly so
// the user can see the output of /time and /summary, which is very helpful
// when trying to figure out why a PDB file is too large.
- if (Error e = builder.commit(config->pdbPath, guid)) {
+ if (Error e = builder.commit(ctx.config.pdbPath, guid)) {
checkError(std::move(e));
- error("failed to write PDB file " + Twine(config->pdbPath));
+ error("failed to write PDB file " + Twine(ctx.config.pdbPath));
}
}
-static uint32_t getSecrelReloc() {
- switch (config->machine) {
+static uint32_t getSecrelReloc(llvm::COFF::MachineTypes machine) {
+ switch (machine) {
case AMD64:
return COFF::IMAGE_REL_AMD64_SECREL;
case I386:
DebugLinesSubsectionRef &lines,
uint32_t &offsetInLinetable) {
ExitOnError exitOnErr;
- uint32_t secrelReloc = getSecrelReloc();
+ const uint32_t secrelReloc = getSecrelReloc(c->file->ctx.config.machine);
for (SectionChunk *dbgC : c->file->getDebugChunks()) {
if (dbgC->getSectionName() != ".debug$S")
}
// Use CodeView line tables to resolve a file and line number for the given
-// offset into the given chunk and return them, or None if a line table was
-// not found.
-Optional<std::pair<StringRef, uint32_t>>
+// offset into the given chunk and return them, or std::nullopt if a line table
+// was not found.
+std::optional<std::pair<StringRef, uint32_t>>
lld::coff::getFileLineCodeView(const SectionChunk *c, uint32_t addr) {
ExitOnError exitOnErr;
uint32_t offsetInLinetable;
if (!findLineTable(c, addr, cvStrTab, checksums, lines, offsetInLinetable))
- return None;
+ return std::nullopt;
- Optional<uint32_t> nameIndex;
- Optional<uint32_t> lineNumber;
- for (LineColumnEntry &entry : lines) {
+ std::optional<uint32_t> nameIndex;
+ std::optional<uint32_t> lineNumber;
+ for (const LineColumnEntry &entry : lines) {
for (const LineNumberEntry &ln : entry.LineNumbers) {
LineInfo li(ln.Flags);
if (ln.Offset > offsetInLinetable) {
}
}
if (!nameIndex)
- return None;
+ return std::nullopt;
StringRef filename = exitOnErr(getFileName(cvStrTab, checksums, *nameIndex));
return std::make_pair(filename, *lineNumber);
}
#define LLD_COFF_PDB_H
#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"
+#include <optional>
-namespace llvm {
-namespace codeview {
+namespace llvm::codeview {
union DebugInfo;
}
-}
namespace lld {
class Timer;
namespace coff {
-class OutputSection;
class SectionChunk;
-class SymbolTable;
+class COFFLinkerContext;
-void createPDB(SymbolTable *symtab,
- llvm::ArrayRef<OutputSection *> outputSections,
- llvm::ArrayRef<uint8_t> sectionTable,
+void createPDB(COFFLinkerContext &ctx, llvm::ArrayRef<uint8_t> sectionTable,
llvm::codeview::DebugInfo *buildId);
-llvm::Optional<std::pair<llvm::StringRef, uint32_t>>
+std::optional<std::pair<llvm::StringRef, uint32_t>>
getFileLineCodeView(const SectionChunk *c, uint32_t addr);
-extern Timer loadGHashTimer;
-extern Timer mergeGHashTimer;
-
} // namespace coff
} // namespace lld
//===----------------------------------------------------------------------===//
#include "SymbolTable.h"
+#include "COFFLinkerContext.h"
#include "Config.h"
#include "Driver.h"
#include "LTO.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
#include "lld/Common/Timer.h"
-#include "llvm/DebugInfo/Symbolize/Symbolize.h"
+#include "llvm/DebugInfo/DIContext.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Object/WindowsMachineFlag.h"
using namespace llvm;
-namespace lld {
-namespace coff {
+namespace lld::coff {
StringRef ltrim1(StringRef s, const char *chars) {
if (!s.empty() && strchr(chars, s[0]))
return s;
}
-static Timer ltoTimer("LTO", Timer::root());
-
-SymbolTable *symtab;
-
void SymbolTable::addFile(InputFile *file) {
log("Reading " + toString(file));
- file->parse();
+ if (file->lazy) {
+ if (auto *f = dyn_cast<BitcodeFile>(file))
+ f->parseLazy();
+ else
+ cast<ObjFile>(file)->parseLazy();
+ } else {
+ file->parse();
+ if (auto *f = dyn_cast<ObjFile>(file)) {
+ ctx.objFileInstances.push_back(f);
+ } else if (auto *f = dyn_cast<BitcodeFile>(file)) {
+ ctx.bitcodeFileInstances.push_back(f);
+ } else if (auto *f = dyn_cast<ImportFile>(file)) {
+ ctx.importFileInstances.push_back(f);
+ }
+ }
MachineTypes mt = file->getMachineType();
- if (config->machine == IMAGE_FILE_MACHINE_UNKNOWN) {
- config->machine = mt;
- } else if (mt != IMAGE_FILE_MACHINE_UNKNOWN && config->machine != mt) {
+ if (ctx.config.machine == IMAGE_FILE_MACHINE_UNKNOWN) {
+ ctx.config.machine = mt;
+ ctx.driver.addWinSysRootLibSearchPaths();
+ } else if (mt != IMAGE_FILE_MACHINE_UNKNOWN && ctx.config.machine != mt) {
error(toString(file) + ": machine type " + machineToStr(mt) +
- " conflicts with " + machineToStr(config->machine));
+ " conflicts with " + machineToStr(ctx.config.machine));
return;
}
- if (auto *f = dyn_cast<ObjFile>(file)) {
- ObjFile::instances.push_back(f);
- } else if (auto *f = dyn_cast<BitcodeFile>(file)) {
- BitcodeFile::instances.push_back(f);
- } else if (auto *f = dyn_cast<ImportFile>(file)) {
- ImportFile::instances.push_back(f);
- }
-
- driver->parseDirectives(file);
+ ctx.driver.parseDirectives(file);
}
-static void errorOrWarn(const Twine &s) {
- if (config->forceUnresolved)
+static void errorOrWarn(const Twine &s, bool forceUnresolved) {
+ if (forceUnresolved)
warn(s);
else
error(s);
l->file->addMember(l->sym);
break;
}
- case Symbol::Kind::LazyObjectKind:
- cast<LazyObject>(s)->file->fetch();
+ case Symbol::Kind::LazyObjectKind: {
+ InputFile *file = cast<LazyObject>(s)->file;
+ file->ctx.symtab.addFile(file);
break;
+ }
case Symbol::Kind::LazyDLLSymbolKind: {
auto *l = cast<LazyDLLSymbol>(s);
l->file->makeImport(l->sym);
return {res};
}
-static Optional<std::pair<StringRef, uint32_t>>
+static std::optional<std::pair<StringRef, uint32_t>>
getFileLineDwarf(const SectionChunk *c, uint32_t addr) {
- Optional<DILineInfo> optionalLineInfo =
+ std::optional<DILineInfo> optionalLineInfo =
c->file->getDILineInfo(addr, c->getSectionNumber() - 1);
if (!optionalLineInfo)
- return None;
+ return std::nullopt;
const DILineInfo &lineInfo = *optionalLineInfo;
if (lineInfo.FileName == DILineInfo::BadString)
- return None;
- return std::make_pair(saver.save(lineInfo.FileName), lineInfo.Line);
+ return std::nullopt;
+ return std::make_pair(saver().save(lineInfo.FileName), lineInfo.Line);
}
-static Optional<std::pair<StringRef, uint32_t>>
+static std::optional<std::pair<StringRef, uint32_t>>
getFileLine(const SectionChunk *c, uint32_t addr) {
// MinGW can optionally use codeview, even if the default is dwarf.
- Optional<std::pair<StringRef, uint32_t>> fileLine =
+ std::optional<std::pair<StringRef, uint32_t>> fileLine =
getFileLineCodeView(c, addr);
// If codeview didn't yield any result, check dwarf in MinGW mode.
- if (!fileLine && config->mingw)
+ if (!fileLine && c->file->ctx.config.mingw)
fileLine = getFileLineDwarf(c, addr);
return fileLine;
}
if (locations.size() >= maxStrings)
continue;
- Optional<std::pair<StringRef, uint32_t>> fileLine =
+ std::optional<std::pair<StringRef, uint32_t>> fileLine =
getFileLine(sc, r.VirtualAddress);
Symbol *sym = getSymbol(sc, r.VirtualAddress);
if (fileLine)
<< "\n>>> ";
os << toString(file);
if (loc.sym)
- os << ":(" << toString(*loc.sym) << ')';
+ os << ":(" << toString(file->ctx, *loc.sym) << ')';
}
return std::make_pair(symbolLocations, numLocations);
}
std::vector<File> files;
};
-static void reportUndefinedSymbol(const UndefinedDiag &undefDiag) {
+static void reportUndefinedSymbol(const COFFLinkerContext &ctx,
+ const UndefinedDiag &undefDiag) {
std::string out;
llvm::raw_string_ostream os(out);
- os << "undefined symbol: " << toString(*undefDiag.sym);
+ os << "undefined symbol: " << toString(ctx, *undefDiag.sym);
const size_t maxUndefReferences = 3;
size_t numDisplayedRefs = 0, numRefs = 0;
for (const UndefinedDiag::File &ref : undefDiag.files) {
- std::vector<std::string> symbolLocations;
- size_t totalLocations = 0;
- std::tie(symbolLocations, totalLocations) = getSymbolLocations(
+ auto [symbolLocations, totalLocations] = getSymbolLocations(
ref.file, ref.symIndex, maxUndefReferences - numDisplayedRefs);
numRefs += totalLocations;
}
if (numDisplayedRefs < numRefs)
os << "\n>>> referenced " << numRefs - numDisplayedRefs << " more times";
- errorOrWarn(os.str());
+ errorOrWarn(os.str(), ctx.config.forceUnresolved);
}
void SymbolTable::loadMinGWSymbols() {
StringRef name = undef->getName();
- if (config->machine == I386 && config->stdcallFixup) {
+ if (ctx.config.machine == I386 && ctx.config.stdcallFixup) {
// Check if we can resolve an undefined decorated symbol by finding
- // the indended target as an undecorated symbol (only with a leading
+ // the intended target as an undecorated symbol (only with a leading
// underscore).
StringRef origName = name;
StringRef baseName = name;
}
// If it's lazy or already defined, hook it up as weak alias.
if (l->isLazy() || isa<Defined>(l)) {
- if (config->warnStdcallFixup)
+ if (ctx.config.warnStdcallFixup)
warn("Resolving " + origName + " by linking to " + newName);
else
log("Resolving " + origName + " by linking to " + newName);
}
}
- if (config->autoImport) {
+ if (ctx.config.autoImport) {
if (name.startswith("__imp_"))
continue;
// If we have an undefined symbol, but we have a lazy symbol we could
// for __imp_<name> instead, and drop the whole .refptr.<name> chunk.
DefinedRegular *refptr =
dyn_cast_or_null<DefinedRegular>(find((".refptr." + name).str()));
- if (refptr && refptr->getChunk()->getSize() == config->wordsize) {
+ if (refptr && refptr->getChunk()->getSize() == ctx.config.wordsize) {
SectionChunk *sc = dyn_cast_or_null<SectionChunk>(refptr->getChunk());
if (sc && sc->getRelocs().size() == 1 && *sc->symbols().begin() == sym) {
log("Replacing .refptr." + name + " with " + imp->getName());
/// defined symbol imported" diagnostic for symbols in localImports.
/// objFiles and bitcodeFiles (if not nullptr) are used to report where
/// undefined symbols are referenced.
-static void
-reportProblemSymbols(const SmallPtrSetImpl<Symbol *> &undefs,
- const DenseMap<Symbol *, Symbol *> *localImports,
- const std::vector<ObjFile *> objFiles,
- const std::vector<BitcodeFile *> *bitcodeFiles) {
-
+static void reportProblemSymbols(
+ const COFFLinkerContext &ctx, const SmallPtrSetImpl<Symbol *> &undefs,
+ const DenseMap<Symbol *, Symbol *> *localImports, bool needBitcodeFiles) {
// Return early if there is nothing to report (which should be
// the common case).
if (undefs.empty() && (!localImports || localImports->empty()))
return;
- for (Symbol *b : config->gcroot) {
+ for (Symbol *b : ctx.config.gcroot) {
if (undefs.count(b))
- errorOrWarn("<root>: undefined symbol: " + toString(*b));
+ errorOrWarn("<root>: undefined symbol: " + toString(ctx, *b),
+ ctx.config.forceUnresolved);
if (localImports)
if (Symbol *imp = localImports->lookup(b))
- warn("<root>: locally defined symbol imported: " + toString(*imp) +
+ warn("<root>: locally defined symbol imported: " + toString(ctx, *imp) +
" (defined in " + toString(imp->getFile()) + ") [LNK4217]");
}
if (localImports)
if (Symbol *imp = localImports->lookup(sym))
warn(toString(file) +
- ": locally defined symbol imported: " + toString(*imp) +
+ ": locally defined symbol imported: " + toString(ctx, *imp) +
" (defined in " + toString(imp->getFile()) + ") [LNK4217]");
}
};
- for (ObjFile *file : objFiles)
+ for (ObjFile *file : ctx.objFileInstances)
processFile(file, file->getSymbols());
- if (bitcodeFiles)
- for (BitcodeFile *file : *bitcodeFiles)
+ if (needBitcodeFiles)
+ for (BitcodeFile *file : ctx.bitcodeFileInstances)
processFile(file, file->getSymbols());
for (const UndefinedDiag &undefDiag : undefDiags)
- reportUndefinedSymbol(undefDiag);
+ reportUndefinedSymbol(ctx, undefDiag);
}
void SymbolTable::reportUnresolvable() {
}
if (name.contains("_PchSym_"))
continue;
- if (config->autoImport && impSymbol(name))
+ if (ctx.config.autoImport && impSymbol(name))
continue;
undefs.insert(sym);
}
- reportProblemSymbols(undefs,
- /* localImports */ nullptr, ObjFile::instances,
- &BitcodeFile::instances);
+ reportProblemSymbols(ctx, undefs,
+ /* localImports */ nullptr, true);
}
void SymbolTable::resolveRemainingUndefines() {
Symbol *imp = find(name.substr(strlen("__imp_")));
if (imp && isa<Defined>(imp)) {
auto *d = cast<Defined>(imp);
- replaceSymbol<DefinedLocalImport>(sym, name, d);
+ replaceSymbol<DefinedLocalImport>(sym, ctx, name, d);
localImportChunks.push_back(cast<DefinedLocalImport>(sym)->getChunk());
localImports[sym] = d;
continue;
if (name.contains("_PchSym_"))
continue;
- if (config->autoImport && handleMinGWAutomaticImport(sym, name))
+ if (ctx.config.autoImport && handleMinGWAutomaticImport(sym, name))
continue;
// Remaining undefined symbols are not fatal if /force is specified.
// They are replaced with dummy defined symbols.
- if (config->forceUnresolved)
- replaceSymbol<DefinedAbsolute>(sym, name, 0);
+ if (ctx.config.forceUnresolved)
+ replaceSymbol<DefinedAbsolute>(sym, ctx, name, 0);
undefs.insert(sym);
}
reportProblemSymbols(
- undefs, config->warnLocallyDefinedImported ? &localImports : nullptr,
- ObjFile::instances, /* bitcode files no longer needed */ nullptr);
+ ctx, undefs,
+ ctx.config.warnLocallyDefinedImported ? &localImports : nullptr, false);
}
std::pair<Symbol *, bool> SymbolTable::insert(StringRef name) {
Symbol *SymbolTable::addUndefined(StringRef name, InputFile *f,
bool isWeakAlias) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(name, f);
+ auto [s, wasInserted] = insert(name, f);
if (wasInserted || (s->isLazy() && isWeakAlias)) {
replaceSymbol<Undefined>(s, name);
return s;
void SymbolTable::addLazyArchive(ArchiveFile *f, const Archive::Symbol &sym) {
StringRef name = sym.getName();
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(name);
+ auto [s, wasInserted] = insert(name);
if (wasInserted) {
replaceSymbol<LazyArchive>(s, f, sym);
return;
f->addMember(sym);
}
-void SymbolTable::addLazyObject(LazyObjFile *f, StringRef n) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n, f);
+void SymbolTable::addLazyObject(InputFile *f, StringRef n) {
+ assert(f->lazy);
+ auto [s, wasInserted] = insert(n, f);
if (wasInserted) {
replaceSymbol<LazyObject>(s, f, n);
return;
if (!u || u->weakAlias || s->pendingArchiveLoad)
return;
s->pendingArchiveLoad = true;
- f->fetch();
+ f->lazy = false;
+ addFile(f);
}
void SymbolTable::addLazyDLLSymbol(DLLFile *f, DLLFile::Symbol *sym,
StringRef n) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n);
+ auto [s, wasInserted] = insert(n);
if (wasInserted) {
replaceSymbol<LazyDLLSymbol>(s, f, sym, n);
return;
static std::string getSourceLocationObj(ObjFile *file, SectionChunk *sc,
uint32_t offset, StringRef name) {
- Optional<std::pair<StringRef, uint32_t>> fileLine;
+ std::optional<std::pair<StringRef, uint32_t>> fileLine;
if (sc)
fileLine = getFileLine(sc, offset);
if (!fileLine)
uint32_t newSectionOffset) {
std::string msg;
llvm::raw_string_ostream os(msg);
- os << "duplicate symbol: " << toString(*existing);
+ os << "duplicate symbol: " << toString(ctx, *existing);
DefinedRegular *d = dyn_cast<DefinedRegular>(existing);
if (d && isa<ObjFile>(d->getFile())) {
os << getSourceLocation(newFile, newSc, newSectionOffset,
existing->getName());
- if (config->forceMultiple)
+ if (ctx.config.forceMultiple)
warn(os.str());
else
error(os.str());
}
Symbol *SymbolTable::addAbsolute(StringRef n, COFFSymbolRef sym) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n, nullptr);
+ auto [s, wasInserted] = insert(n, nullptr);
s->isUsedInRegularObj = true;
if (wasInserted || isa<Undefined>(s) || s->isLazy())
- replaceSymbol<DefinedAbsolute>(s, n, sym);
+ replaceSymbol<DefinedAbsolute>(s, ctx, n, sym);
else if (auto *da = dyn_cast<DefinedAbsolute>(s)) {
if (da->getVA() != sym.getValue())
reportDuplicate(s, nullptr);
}
Symbol *SymbolTable::addAbsolute(StringRef n, uint64_t va) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n, nullptr);
+ auto [s, wasInserted] = insert(n, nullptr);
s->isUsedInRegularObj = true;
if (wasInserted || isa<Undefined>(s) || s->isLazy())
- replaceSymbol<DefinedAbsolute>(s, n, va);
+ replaceSymbol<DefinedAbsolute>(s, ctx, n, va);
else if (auto *da = dyn_cast<DefinedAbsolute>(s)) {
if (da->getVA() != va)
reportDuplicate(s, nullptr);
}
Symbol *SymbolTable::addSynthetic(StringRef n, Chunk *c) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n, nullptr);
+ auto [s, wasInserted] = insert(n, nullptr);
s->isUsedInRegularObj = true;
if (wasInserted || isa<Undefined>(s) || s->isLazy())
replaceSymbol<DefinedSynthetic>(s, n, c);
Symbol *SymbolTable::addRegular(InputFile *f, StringRef n,
const coff_symbol_generic *sym, SectionChunk *c,
- uint32_t sectionOffset) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n, f);
- if (wasInserted || !isa<DefinedRegular>(s))
+ uint32_t sectionOffset, bool isWeak) {
+ auto [s, wasInserted] = insert(n, f);
+ if (wasInserted || !isa<DefinedRegular>(s) || s->isWeak)
replaceSymbol<DefinedRegular>(s, f, n, /*IsCOMDAT*/ false,
- /*IsExternal*/ true, sym, c);
- else
+ /*IsExternal*/ true, sym, c, isWeak);
+ else if (!isWeak)
reportDuplicate(s, f, c, sectionOffset);
return s;
}
std::pair<DefinedRegular *, bool>
SymbolTable::addComdat(InputFile *f, StringRef n,
const coff_symbol_generic *sym) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n, f);
+ auto [s, wasInserted] = insert(n, f);
if (wasInserted || !isa<DefinedRegular>(s)) {
replaceSymbol<DefinedRegular>(s, f, n, /*IsCOMDAT*/ true,
/*IsExternal*/ true, sym, nullptr);
Symbol *SymbolTable::addCommon(InputFile *f, StringRef n, uint64_t size,
const coff_symbol_generic *sym, CommonChunk *c) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n, f);
+ auto [s, wasInserted] = insert(n, f);
if (wasInserted || !isa<DefinedCOFF>(s))
replaceSymbol<DefinedCommon>(s, f, n, size, sym, c);
else if (auto *dc = dyn_cast<DefinedCommon>(s))
}
Symbol *SymbolTable::addImportData(StringRef n, ImportFile *f) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(n, nullptr);
+ auto [s, wasInserted] = insert(n, nullptr);
s->isUsedInRegularObj = true;
if (wasInserted || isa<Undefined>(s) || s->isLazy()) {
replaceSymbol<DefinedImportData>(s, n, f);
Symbol *SymbolTable::addImportThunk(StringRef name, DefinedImportData *id,
uint16_t machine) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(name, nullptr);
+ auto [s, wasInserted] = insert(name, nullptr);
s->isUsedInRegularObj = true;
if (wasInserted || isa<Undefined>(s) || s->isLazy()) {
- replaceSymbol<DefinedImportThunk>(s, name, id, machine);
+ replaceSymbol<DefinedImportThunk>(s, ctx, name, id, machine);
return s;
}
}
}
-std::vector<Chunk *> SymbolTable::getChunks() {
+std::vector<Chunk *> SymbolTable::getChunks() const {
std::vector<Chunk *> res;
- for (ObjFile *file : ObjFile::instances) {
+ for (ObjFile *file : ctx.objFileInstances) {
ArrayRef<Chunk *> v = file->getChunks();
res.insert(res.end(), v.begin(), v.end());
}
return res;
}
-Symbol *SymbolTable::find(StringRef name) {
+Symbol *SymbolTable::find(StringRef name) const {
return symMap.lookup(CachedHashStringRef(name));
}
-Symbol *SymbolTable::findUnderscore(StringRef name) {
- if (config->machine == I386)
+Symbol *SymbolTable::findUnderscore(StringRef name) const {
+ if (ctx.config.machine == I386)
return find(("_" + name).str());
return find(name);
}
}
Symbol *SymbolTable::findMangle(StringRef name) {
- if (Symbol *sym = find(name))
- if (!isa<Undefined>(sym))
+ if (Symbol *sym = find(name)) {
+ if (auto *u = dyn_cast<Undefined>(sym)) {
+ // We're specifically looking for weak aliases that ultimately resolve to
+ // defined symbols, hence the call to getWeakAlias() instead of just using
+ // the weakAlias member variable. This matches link.exe's behavior.
+ if (Symbol *weakAlias = u->getWeakAlias())
+ return weakAlias;
+ } else {
return sym;
+ }
+ }
// Efficient fuzzy string lookup is impossible with a hash table, so iterate
// the symbol table once and collect all possibly matching symbols into this
};
// For non-x86, just look for C++ functions.
- if (config->machine != I386)
+ if (ctx.config.machine != I386)
return findByPrefix("?" + name + "@@Y");
if (!name.startswith("_"))
return addUndefined(name, nullptr, false);
}
-void SymbolTable::addCombinedLTOObjects() {
- if (BitcodeFile::instances.empty())
+void SymbolTable::compileBitcodeFiles() {
+ if (ctx.bitcodeFileInstances.empty())
return;
- ScopedTimer t(ltoTimer);
- lto.reset(new BitcodeCompiler);
- for (BitcodeFile *f : BitcodeFile::instances)
+ ScopedTimer t(ctx.ltoTimer);
+ lto.reset(new BitcodeCompiler(ctx));
+ for (BitcodeFile *f : ctx.bitcodeFileInstances)
lto->add(*f);
for (InputFile *newObj : lto->compile()) {
ObjFile *obj = cast<ObjFile>(newObj);
obj->parse();
- ObjFile::instances.push_back(obj);
+ ctx.objFileInstances.push_back(obj);
}
}
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
struct LTOCodeGenerator;
}
-namespace lld {
-namespace coff {
+namespace lld::coff {
class Chunk;
class CommonChunk;
+class COFFLinkerContext;
class Defined;
class DefinedAbsolute;
class DefinedRegular;
// There is one add* function per symbol type.
class SymbolTable {
public:
+ SymbolTable(COFFLinkerContext &c) : ctx(c) {}
+
void addFile(InputFile *file);
// Emit errors for symbols that cannot be resolved.
bool handleMinGWAutomaticImport(Symbol *sym, StringRef name);
// Returns a list of chunks of selected symbols.
- std::vector<Chunk *> getChunks();
+ std::vector<Chunk *> getChunks() const;
// Returns a symbol for a given name. Returns a nullptr if not found.
- Symbol *find(StringRef name);
- Symbol *findUnderscore(StringRef name);
+ Symbol *find(StringRef name) const;
+ Symbol *findUnderscore(StringRef name) const;
// Occasionally we have to resolve an undefined symbol to its
// mangled symbol. This function tries to find a mangled name
// Build a set of COFF objects representing the combined contents of
// BitcodeFiles and add them to the symbol table. Called after all files are
// added and before the writer writes results to a file.
- void addCombinedLTOObjects();
+ void compileBitcodeFiles();
// Creates an Undefined symbol for a given name.
Symbol *addUndefined(StringRef name);
Symbol *addUndefined(StringRef name, InputFile *f, bool isWeakAlias);
void addLazyArchive(ArchiveFile *f, const Archive::Symbol &sym);
- void addLazyObject(LazyObjFile *f, StringRef n);
+ void addLazyObject(InputFile *f, StringRef n);
void addLazyDLLSymbol(DLLFile *f, DLLFile::Symbol *sym, StringRef n);
Symbol *addAbsolute(StringRef n, COFFSymbolRef s);
Symbol *addRegular(InputFile *f, StringRef n,
const llvm::object::coff_symbol_generic *s = nullptr,
- SectionChunk *c = nullptr, uint32_t sectionOffset = 0);
+ SectionChunk *c = nullptr, uint32_t sectionOffset = 0,
+ bool isWeak = false);
std::pair<DefinedRegular *, bool>
addComdat(InputFile *f, StringRef n,
const llvm::object::coff_symbol_generic *s = nullptr);
llvm::DenseMap<llvm::CachedHashStringRef, Symbol *> symMap;
std::unique_ptr<BitcodeCompiler> lto;
-};
-extern SymbolTable *symtab;
+ COFFLinkerContext &ctx;
+};
std::vector<std::string> getSymbolLocations(ObjFile *file, uint32_t symIndex);
StringRef ltrim1(StringRef s, const char *chars);
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
//===----------------------------------------------------------------------===//
#include "Symbols.h"
+#include "COFFLinkerContext.h"
#include "InputFiles.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
"symbols should be optimized for memory usage");
// Returns a symbol name for an error message.
-static std::string maybeDemangleSymbol(StringRef symName) {
- if (config->demangle) {
+static std::string maybeDemangleSymbol(const COFFLinkerContext &ctx,
+ StringRef symName) {
+ if (ctx.config.demangle) {
std::string prefix;
StringRef prefixless = symName;
if (prefixless.consume_front("__imp_"))
prefix = "__declspec(dllimport) ";
StringRef demangleInput = prefixless;
- if (config->machine == I386)
+ if (ctx.config.machine == I386)
demangleInput.consume_front("_");
- std::string demangled = demangle(std::string(demangleInput));
+ std::string demangled = demangle(demangleInput.str());
if (demangled != demangleInput)
- return prefix + demangle(std::string(demangleInput));
+ return prefix + demangle(demangleInput.str());
return (prefix + prefixless).str();
}
return std::string(symName);
}
-std::string toString(coff::Symbol &b) {
- return maybeDemangleSymbol(b.getName());
+std::string toString(const COFFLinkerContext &ctx, coff::Symbol &b) {
+ return maybeDemangleSymbol(ctx, b.getName());
}
-std::string toCOFFString(const Archive::Symbol &b) {
- return maybeDemangleSymbol(b.getName());
+std::string toCOFFString(const COFFLinkerContext &ctx,
+ const Archive::Symbol &b) {
+ return maybeDemangleSymbol(ctx, b.getName());
}
namespace coff {
return COFFSymbolRef(reinterpret_cast<const coff_symbol32 *>(sym));
}
-uint16_t DefinedAbsolute::numOutputSections;
+uint64_t DefinedAbsolute::getRVA() { return va - ctx.config.imageBase; }
-static Chunk *makeImportThunk(DefinedImportData *s, uint16_t machine) {
+static Chunk *makeImportThunk(COFFLinkerContext &ctx, DefinedImportData *s,
+ uint16_t machine) {
if (machine == AMD64)
- return make<ImportThunkChunkX64>(s);
+ return make<ImportThunkChunkX64>(ctx, s);
if (machine == I386)
- return make<ImportThunkChunkX86>(s);
+ return make<ImportThunkChunkX86>(ctx, s);
if (machine == ARM64)
- return make<ImportThunkChunkARM64>(s);
+ return make<ImportThunkChunkARM64>(ctx, s);
assert(machine == ARMNT);
- return make<ImportThunkChunkARM>(s);
+ return make<ImportThunkChunkARM>(ctx, s);
}
-DefinedImportThunk::DefinedImportThunk(StringRef name, DefinedImportData *s,
- uint16_t machine)
+DefinedImportThunk::DefinedImportThunk(COFFLinkerContext &ctx, StringRef name,
+ DefinedImportData *s, uint16_t machine)
: Defined(DefinedImportThunkKind, name), wrappedSym(s),
- data(makeImportThunk(s, machine)) {}
+ data(makeImportThunk(ctx, s, machine)) {}
Defined *Undefined::getWeakAlias() {
// A weak alias may be a weak alias to another symbol, so check recursively.
MemoryBufferRef LazyArchive::getMemberBuffer() {
Archive::Child c =
- CHECK(sym.getMember(),
- "could not get the member for symbol " + toCOFFString(sym));
+ CHECK(sym.getMember(), "could not get the member for symbol " +
+ toCOFFString(file->ctx, sym));
return CHECK(c.getMemoryBufferRef(),
- "could not get the buffer for the member defining symbol " +
- toCOFFString(sym));
+ "could not get the buffer for the member defining symbol " +
+ toCOFFString(file->ctx, sym));
}
} // namespace coff
} // namespace lld
namespace lld {
-std::string toString(coff::Symbol &b);
-
-// There are two different ways to convert an Archive::Symbol to a string:
-// One for Microsoft name mangling and one for Itanium name mangling.
-// Call the functions toCOFFString and toELFString, not just toString.
-std::string toCOFFString(const coff::Archive::Symbol &b);
-
namespace coff {
using llvm::object::Archive;
using llvm::object::coff_symbol_generic;
class ArchiveFile;
+class COFFLinkerContext;
class InputFile;
class ObjFile;
class SymbolTable;
: symbolKind(k), isExternal(true), isCOMDAT(false),
writtenToSymtab(false), pendingArchiveLoad(false), isGCRoot(false),
isRuntimePseudoReloc(false), deferUndefined(false), canInline(true),
- nameSize(n.size()), nameData(n.empty() ? nullptr : n.data()) {}
+ isWeak(false), nameSize(n.size()),
+ nameData(n.empty() ? nullptr : n.data()) {
+ assert((!n.empty() || k <= LastDefinedCOFFKind) &&
+ "If the name is empty, the Symbol must be a DefinedCOFF.");
+ }
const unsigned symbolKind : 8;
unsigned isExternal : 1;
// doesn't know the final contents of the symbol.
unsigned canInline : 1;
+ // True if the symbol is weak. This is only tracked for bitcode/LTO symbols.
+ // This information isn't written to the output; rather, it's used for
+ // managing weak symbol overrides.
+ unsigned isWeak : 1;
+
protected:
// Symbol name length. Assume symbol lengths fit in a 32-bit integer.
uint32_t nameSize;
DefinedRegular(InputFile *f, StringRef n, bool isCOMDAT,
bool isExternal = false,
const coff_symbol_generic *s = nullptr,
- SectionChunk *c = nullptr)
+ SectionChunk *c = nullptr, bool isWeak = false)
: DefinedCOFF(DefinedRegularKind, f, n, s), data(c ? &c->repl : nullptr) {
this->isExternal = isExternal;
this->isCOMDAT = isCOMDAT;
+ this->isWeak = isWeak;
}
static bool classof(const Symbol *s) {
// Absolute symbols.
class DefinedAbsolute : public Defined {
public:
- DefinedAbsolute(StringRef n, COFFSymbolRef s)
- : Defined(DefinedAbsoluteKind, n), va(s.getValue()) {
+ DefinedAbsolute(const COFFLinkerContext &c, StringRef n, COFFSymbolRef s)
+ : Defined(DefinedAbsoluteKind, n), va(s.getValue()), ctx(c) {
isExternal = s.isExternal();
}
- DefinedAbsolute(StringRef n, uint64_t v)
- : Defined(DefinedAbsoluteKind, n), va(v) {}
+ DefinedAbsolute(const COFFLinkerContext &c, StringRef n, uint64_t v)
+ : Defined(DefinedAbsoluteKind, n), va(v), ctx(c) {}
static bool classof(const Symbol *s) {
return s->kind() == DefinedAbsoluteKind;
}
- uint64_t getRVA() { return va - config->imageBase; }
+ uint64_t getRVA();
void setVA(uint64_t v) { va = v; }
uint64_t getVA() const { return va; }
- // Section index relocations against absolute symbols resolve to
- // this 16 bit number, and it is the largest valid section index
- // plus one. This variable keeps it.
- static uint16_t numOutputSections;
-
private:
uint64_t va;
+ const COFFLinkerContext &ctx;
};
// This symbol is used for linker-synthesized symbols like __ImageBase and
class LazyObject : public Symbol {
public:
- LazyObject(LazyObjFile *f, StringRef n)
- : Symbol(LazyObjectKind, n), file(f) {}
+ LazyObject(InputFile *f, StringRef n) : Symbol(LazyObjectKind, n), file(f) {}
static bool classof(const Symbol *s) { return s->kind() == LazyObjectKind; }
- LazyObjFile *file;
+ InputFile *file;
};
// MinGW only.
// a regular name. A function pointer is given as a DefinedImportData.
class DefinedImportThunk : public Defined {
public:
- DefinedImportThunk(StringRef name, DefinedImportData *s, uint16_t machine);
+ DefinedImportThunk(COFFLinkerContext &ctx, StringRef name,
+ DefinedImportData *s, uint16_t machine);
static bool classof(const Symbol *s) {
return s->kind() == DefinedImportThunkKind;
// This is here just for compatibility with MSVC.
class DefinedLocalImport : public Defined {
public:
- DefinedLocalImport(StringRef n, Defined *s)
- : Defined(DefinedLocalImportKind, n), data(make<LocalImportChunk>(s)) {}
+ DefinedLocalImport(COFFLinkerContext &ctx, StringRef n, Defined *s)
+ : Defined(DefinedLocalImportKind, n),
+ data(make<LocalImportChunk>(ctx, s)) {}
static bool classof(const Symbol *s) {
return s->kind() == DefinedLocalImportKind;
}
} // namespace coff
+std::string toString(const coff::COFFLinkerContext &ctx, coff::Symbol &b);
+std::string toCOFFString(const coff::COFFLinkerContext &ctx,
+ const llvm::object::Archive::Symbol &b);
+
} // namespace lld
#endif
#define LLD_COFF_TYPEMERGER_H
#include "Config.h"
+#include "DebugTypes.h"
+#include "lld/Common/Timer.h"
#include "llvm/DebugInfo/CodeView/MergingTypeTableBuilder.h"
#include "llvm/DebugInfo/CodeView/TypeHashing.h"
#include "llvm/Support/Allocator.h"
#include <atomic>
-namespace lld {
-namespace coff {
+namespace lld::coff {
using llvm::codeview::GloballyHashedType;
using llvm::codeview::TypeIndex;
class TypeMerger {
public:
- TypeMerger(llvm::BumpPtrAllocator &alloc);
+ TypeMerger(COFFLinkerContext &ctx, llvm::BumpPtrAllocator &alloc);
~TypeMerger();
/// Get the type table or the global type table if /DEBUG:GHASH is enabled.
inline llvm::codeview::TypeCollection &getTypeTable() {
- assert(!config->debugGHashes);
+ assert(!ctx.config.debugGHashes);
return typeTable;
}
/// Get the ID table or the global ID table if /DEBUG:GHASH is enabled.
inline llvm::codeview::TypeCollection &getIDTable() {
- assert(!config->debugGHashes);
+ assert(!ctx.config.debugGHashes);
return idTable;
}
// keyed by type index.
SmallVector<uint32_t, 0> tpiCounts;
SmallVector<uint32_t, 0> ipiCounts;
+
+ /// Dependency type sources, such as type servers or PCH object files. These
+ /// must be processed before objects that rely on them. Set by
+ /// sortDependencies.
+ ArrayRef<TpiSource *> dependencySources;
+
+ /// Object file sources. These must be processed after dependencySources.
+ ArrayRef<TpiSource *> objectSources;
+
+ /// Sorts the dependencies and reassigns TpiSource indices.
+ void sortDependencies();
+
+private:
+ void clearGHashes();
+
+ COFFLinkerContext &ctx;
};
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
//===----------------------------------------------------------------------===//
#include "Writer.h"
+#include "COFFLinkerContext.h"
#include "CallGraphSort.h"
#include "Config.h"
#include "DLL.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSet.h"
-#include "llvm/ADT/StringSwitch.h"
+#include "llvm/BinaryFormat/COFF.h"
#include "llvm/Support/BinaryStreamReader.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Endian.h"
static const int numberOfDataDirectory = 16;
-// Global vector of all output sections. After output sections are finalized,
-// this can be indexed by Chunk::getOutputSection.
-static std::vector<OutputSection *> outputSections;
-
-OutputSection *Chunk::getOutputSection() const {
- return osidx == 0 ? nullptr : outputSections[osidx - 1];
-}
-
-void OutputSection::clear() { outputSections.clear(); }
-
namespace {
class DebugDirectoryChunk : public NonSectionChunk {
public:
- DebugDirectoryChunk(const std::vector<std::pair<COFF::DebugType, Chunk *>> &r,
+ DebugDirectoryChunk(const COFFLinkerContext &c,
+ const std::vector<std::pair<COFF::DebugType, Chunk *>> &r,
bool writeRepro)
- : records(r), writeRepro(writeRepro) {}
+ : records(r), writeRepro(writeRepro), ctx(c) {}
size_t getSize() const override {
return (records.size() + int(writeRepro)) * sizeof(debug_directory);
for (const std::pair<COFF::DebugType, Chunk *>& record : records) {
Chunk *c = record.second;
- OutputSection *os = c->getOutputSection();
+ const OutputSection *os = ctx.getOutputSection(c);
uint64_t offs = os->getFileOff() + (c->getRVA() - os->getRVA());
fillEntry(d, record.first, c->getSize(), c->getRVA(), offs);
++d;
mutable std::vector<support::ulittle32_t *> timeDateStamps;
const std::vector<std::pair<COFF::DebugType, Chunk *>> &records;
bool writeRepro;
+ const COFFLinkerContext &ctx;
};
class CVDebugRecordChunk : public NonSectionChunk {
public:
+ CVDebugRecordChunk(const COFFLinkerContext &c) : ctx(c) {}
+
size_t getSize() const override {
- return sizeof(codeview::DebugInfo) + config->pdbAltPath.size() + 1;
+ return sizeof(codeview::DebugInfo) + ctx.config.pdbAltPath.size() + 1;
}
void writeTo(uint8_t *b) const override {
// variable sized field (PDB Path)
char *p = reinterpret_cast<char *>(b + sizeof(*buildId));
- if (!config->pdbAltPath.empty())
- memcpy(p, config->pdbAltPath.data(), config->pdbAltPath.size());
- p[config->pdbAltPath.size()] = '\0';
+ if (!ctx.config.pdbAltPath.empty())
+ memcpy(p, ctx.config.pdbAltPath.data(), ctx.config.pdbAltPath.size());
+ p[ctx.config.pdbAltPath.size()] = '\0';
}
mutable codeview::DebugInfo *buildId = nullptr;
+
+private:
+ const COFFLinkerContext &ctx;
};
class ExtendedDllCharacteristicsChunk : public NonSectionChunk {
bool operator<(const PartialSectionKey &other) const {
int c = name.compare(other.name);
- if (c == 1)
+ if (c > 0)
return false;
if (c == 0)
return characteristics < other.characteristics;
// The writer writes a SymbolTable result to a file.
class Writer {
public:
- Writer() : buffer(errorHandler().outputBuffer) {}
+ Writer(COFFLinkerContext &c)
+ : buffer(errorHandler().outputBuffer), delayIdata(c), edata(c), ctx(c) {}
void run();
private:
void mergeSections();
void removeUnusedSections();
void assignAddresses();
+ bool isInRange(uint16_t relType, uint64_t s, uint64_t p, int margin);
+ std::pair<Defined *, bool> getThunk(DenseMap<uint64_t, Defined *> &lastThunks,
+ Defined *target, uint64_t p,
+ uint16_t type, int margin);
+ bool createThunks(OutputSection *os, int margin);
+ bool verifyRanges(const std::vector<Chunk *> chunks);
void finalizeAddresses();
void removeEmptySections();
void assignOutputSectionIndices();
void createSEHTable();
void createRuntimePseudoRelocs();
void insertCtorDtorSymbols();
+ void markSymbolsWithRelocations(ObjFile *file, SymbolRVASet &usedSymbols);
void createGuardCFTables();
void markSymbolsForRVATable(ObjFile *file,
ArrayRef<SectionChunk *> symIdxChunks,
void setSectionPermissions();
void writeSections();
void writeBuildId();
+ void writePEChecksum();
void sortSections();
void sortExceptionTable();
void sortCRTSectionChunks(std::vector<Chunk *> &chunks);
void addSyntheticIdata();
+ void sortBySectionOrder(std::vector<Chunk *> &chunks);
void fixPartialSectionChars(StringRef name, uint32_t chars);
bool fixGnuImportChunks();
void fixTlsAlignment();
PartialSection *createPartialSection(StringRef name, uint32_t outChars);
PartialSection *findPartialSection(StringRef name, uint32_t outChars);
- llvm::Optional<coff_symbol16> createSymbol(Defined *d);
+ std::optional<coff_symbol16> createSymbol(Defined *d);
size_t addEntryToStringTable(StringRef str);
OutputSection *findSection(StringRef name);
uint32_t getSizeOfInitializedData();
+ void checkLoadConfig();
+ template <typename T> void checkLoadConfigGuardData(const T *loadConfig);
+
std::unique_ptr<FileOutputBuffer> &buffer;
std::map<PartialSectionKey, PartialSection *> partialSections;
std::vector<char> strtab;
// files, so we need to keep track of them separately.
Chunk *firstPdata = nullptr;
Chunk *lastPdata;
+
+ COFFLinkerContext &ctx;
};
} // anonymous namespace
-static Timer codeLayoutTimer("Code Layout", Timer::root());
-static Timer diskCommitTimer("Commit Output File", Timer::root());
-
-void lld::coff::writeResult() { Writer().run(); }
+void lld::coff::writeResult(COFFLinkerContext &ctx) { Writer(ctx).run(); }
void OutputSection::addChunk(Chunk *c) {
chunks.push_back(c);
}
// Write the section header to a given buffer.
-void OutputSection::writeHeaderTo(uint8_t *buf) {
+void OutputSection::writeHeaderTo(uint8_t *buf, bool isDebug) {
auto *hdr = reinterpret_cast<coff_section *>(buf);
*hdr = header;
if (stringTableOff) {
// If name is too long, write offset into the string table as a name.
- sprintf(hdr->Name, "/%d", stringTableOff);
+ encodeSectionName(hdr->Name, stringTableOff);
} else {
- assert(!config->debug || name.size() <= COFF::NameSize ||
+ assert(!isDebug || name.size() <= COFF::NameSize ||
(hdr->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0);
strncpy(hdr->Name, name.data(),
std::min(name.size(), (size_t)COFF::NameSize));
// Check whether the target address S is in range from a relocation
// of type relType at address P.
-static bool isInRange(uint16_t relType, uint64_t s, uint64_t p, int margin) {
- if (config->machine == ARMNT) {
+bool Writer::isInRange(uint16_t relType, uint64_t s, uint64_t p, int margin) {
+ if (ctx.config.machine == ARMNT) {
int64_t diff = AbsoluteDifference(s, p + 4) + margin;
switch (relType) {
case IMAGE_REL_ARM_BRANCH20T:
default:
return true;
}
- } else if (config->machine == ARM64) {
+ } else if (ctx.config.machine == ARM64) {
int64_t diff = AbsoluteDifference(s, p) + margin;
switch (relType) {
case IMAGE_REL_ARM64_BRANCH26:
// Return the last thunk for the given target if it is in range,
// or create a new one.
-static std::pair<Defined *, bool>
-getThunk(DenseMap<uint64_t, Defined *> &lastThunks, Defined *target, uint64_t p,
- uint16_t type, int margin) {
+std::pair<Defined *, bool>
+Writer::getThunk(DenseMap<uint64_t, Defined *> &lastThunks, Defined *target,
+ uint64_t p, uint16_t type, int margin) {
Defined *&lastThunk = lastThunks[target->getRVA()];
if (lastThunk && isInRange(type, lastThunk->getRVA(), p, margin))
return {lastThunk, false};
Chunk *c;
- switch (config->machine) {
+ switch (ctx.config.machine) {
case ARMNT:
- c = make<RangeExtensionThunkARM>(target);
+ c = make<RangeExtensionThunkARM>(ctx, target);
break;
case ARM64:
- c = make<RangeExtensionThunkARM64>(target);
+ c = make<RangeExtensionThunkARM64>(ctx, target);
break;
default:
llvm_unreachable("Unexpected architecture");
}
- Defined *d = make<DefinedSynthetic>("", c);
+ Defined *d = make<DefinedSynthetic>("range_extension_thunk", c);
lastThunk = d;
return {d, true};
}
// After adding thunks, we verify that all relocations are in range (with
// no extra margin requirements). If this failed, we restart (throwing away
// the previously created thunks) and retry with a wider margin.
-static bool createThunks(OutputSection *os, int margin) {
+bool Writer::createThunks(OutputSection *os, int margin) {
bool addressesChanged = false;
DenseMap<uint64_t, Defined *> lastThunks;
DenseMap<std::pair<ObjFile *, Defined *>, uint32_t> thunkSymtabIndices;
if (isInRange(rel.Type, s, p, margin))
continue;
- // If the target isn't in range, hook it up to an existing or new
- // thunk.
- Defined *thunk;
- bool wasNew;
- std::tie(thunk, wasNew) = getThunk(lastThunks, sym, p, rel.Type, margin);
+ // If the target isn't in range, hook it up to an existing or new thunk.
+ auto [thunk, wasNew] = getThunk(lastThunks, sym, p, rel.Type, margin);
if (wasNew) {
Chunk *thunkChunk = thunk->getChunk();
thunkChunk->setRVA(
ArrayRef<coff_relocation> curRelocs = sc->getRelocs();
MutableArrayRef<coff_relocation> newRelocs;
if (originalRelocs.data() == curRelocs.data()) {
- newRelocs = makeMutableArrayRef(
- bAlloc.Allocate<coff_relocation>(originalRelocs.size()),
+ newRelocs = MutableArrayRef(
+ bAlloc().Allocate<coff_relocation>(originalRelocs.size()),
originalRelocs.size());
} else {
- newRelocs = makeMutableArrayRef(
+ newRelocs = MutableArrayRef(
const_cast<coff_relocation *>(curRelocs.data()), curRelocs.size());
}
}
// Verify that all relocations are in range, with no extra margin requirements.
-static bool verifyRanges(const std::vector<Chunk *> chunks) {
+bool Writer::verifyRanges(const std::vector<Chunk *> chunks) {
for (Chunk *c : chunks) {
SectionChunk *sc = dyn_cast_or_null<SectionChunk>(c);
if (!sc)
// Assign addresses and add thunks if necessary.
void Writer::finalizeAddresses() {
assignAddresses();
- if (config->machine != ARMNT && config->machine != ARM64)
+ if (ctx.config.machine != ARMNT && ctx.config.machine != ARM64)
return;
size_t origNumChunks = 0;
- for (OutputSection *sec : outputSections) {
+ for (OutputSection *sec : ctx.outputSections) {
sec->origChunks = sec->chunks;
origNumChunks += sec->chunks.size();
}
// adding them turned out ok.
bool rangesOk = true;
size_t numChunks = 0;
- for (OutputSection *sec : outputSections) {
+ for (OutputSection *sec : ctx.outputSections) {
if (!verifyRanges(sec->chunks)) {
rangesOk = false;
break;
// If the previous pass didn't work out, reset everything back to the
// original conditions before retrying with a wider margin. This should
// ideally never happen under real circumstances.
- for (OutputSection *sec : outputSections)
+ for (OutputSection *sec : ctx.outputSections)
sec->chunks = sec->origChunks;
margin *= 2;
}
// Try adding thunks everywhere where it is needed, with a margin
// to avoid things going out of range due to the added thunks.
bool addressesChanged = false;
- for (OutputSection *sec : outputSections)
+ for (OutputSection *sec : ctx.outputSections)
addressesChanged |= createThunks(sec, margin);
// If the verification above thought we needed thunks, we should have
// added some.
}
}
+void Writer::writePEChecksum() {
+ if (!ctx.config.writeCheckSum) {
+ return;
+ }
+
+ // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#checksum
+ uint32_t *buf = (uint32_t *)buffer->getBufferStart();
+ uint32_t size = (uint32_t)(buffer->getBufferSize());
+
+ coff_file_header *coffHeader =
+ (coff_file_header *)((uint8_t *)buf + dosStubSize + sizeof(PEMagic));
+ pe32_header *peHeader =
+ (pe32_header *)((uint8_t *)coffHeader + sizeof(coff_file_header));
+
+ uint64_t sum = 0;
+ uint32_t count = size;
+ ulittle16_t *addr = (ulittle16_t *)buf;
+
+ // The PE checksum algorithm, implemented as suggested in RFC1071
+ while (count > 1) {
+ sum += *addr++;
+ count -= 2;
+ }
+
+ // Add left-over byte, if any
+ if (count > 0)
+ sum += *(unsigned char *)addr;
+
+ // Fold 32-bit sum to 16 bits
+ while (sum >> 16) {
+ sum = (sum & 0xffff) + (sum >> 16);
+ }
+
+ sum += size;
+ peHeader->CheckSum = sum;
+}
+
// The main function of the writer.
void Writer::run() {
- ScopedTimer t1(codeLayoutTimer);
+ ScopedTimer t1(ctx.codeLayoutTimer);
createImportTables();
createSections();
fatal("image size (" + Twine(fileSize) + ") " +
"exceeds maximum allowable size (" + Twine(UINT32_MAX) + ")");
- openFile(config->outputFile);
- if (config->is64()) {
+ openFile(ctx.config.outputFile);
+ if (ctx.config.is64()) {
writeHeader<pe32plus_header>();
} else {
writeHeader<pe32_header>();
}
writeSections();
+ checkLoadConfig();
sortExceptionTable();
// Fix up the alignment in the TLS Directory's characteristic field,
t1.stop();
- if (!config->pdbPath.empty() && config->debug) {
+ if (!ctx.config.pdbPath.empty() && ctx.config.debug) {
assert(buildId);
- createPDB(symtab, outputSections, sectionTable, buildId->buildId);
+ createPDB(ctx, sectionTable, buildId->buildId);
}
writeBuildId();
- writeLLDMapFile(outputSections);
- writeMapFile(outputSections);
+ writeLLDMapFile(ctx);
+ writeMapFile(ctx);
+
+ writePEChecksum();
if (errorCount())
return;
- ScopedTimer t2(diskCommitTimer);
+ ScopedTimer t2(ctx.outputCommitTimer);
if (auto e = buffer->commit())
- fatal("failed to write the output file: " + toString(std::move(e)));
+ fatal("failed to write output '" + buffer->getPath() +
+ "': " + toString(std::move(e)));
}
static StringRef getOutputSectionName(StringRef name) {
}
// For /order.
-static void sortBySectionOrder(std::vector<Chunk *> &chunks) {
- auto getPriority = [](const Chunk *c) {
+void Writer::sortBySectionOrder(std::vector<Chunk *> &chunks) {
+ auto getPriority = [&ctx = ctx](const Chunk *c) {
if (auto *sec = dyn_cast<SectionChunk>(c))
if (sec->sym)
- return config->order.lookup(sec->sym->getName());
+ return ctx.config.order.lookup(sec->sym->getName());
return 0;
};
bool hasIdata = false;
// Sort all .idata$* chunks, grouping chunks from the same library,
- // with alphabetical ordering of the object fils within a library.
+ // with alphabetical ordering of the object files within a library.
for (auto it : partialSections) {
PartialSection *pSec = it.second;
if (!pSec->name.startswith(".idata"))
// terminator in .idata$2.
void Writer::addSyntheticIdata() {
uint32_t rdata = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;
- idata.create();
+ idata.create(ctx);
// Add the .idata content in the right section groups, to allow
// chunks from other linked in object files to be grouped together.
// Return whether a SectionChunk's suffix (the dollar and any trailing
// suffix) should be removed and sorted into the main suffixless
// PartialSection.
-static bool shouldStripSectionSuffix(SectionChunk *sc, StringRef name) {
+static bool shouldStripSectionSuffix(SectionChunk *sc, StringRef name,
+ bool isMinGW) {
// On MinGW, comdat groups are formed by putting the comdat group name
// after the '$' in the section name. For .eh_frame$<symbol>, that must
// still be sorted before the .eh_frame trailer from crtend.o, thus just
// hypothetical case of comdat .CRT$XCU, we definitely need to keep the
// suffix for sorting. Thus, to play it safe, only strip the suffix for
// the standard sections.
- if (!config->mingw)
+ if (!isMinGW)
return false;
if (!sc || !sc->isCOMDAT())
return false;
}
void Writer::sortSections() {
- if (!config->callGraphProfile.empty()) {
- DenseMap<const SectionChunk *, int> order = computeCallGraphProfileOrder();
+ if (!ctx.config.callGraphProfile.empty()) {
+ DenseMap<const SectionChunk *, int> order =
+ computeCallGraphProfileOrder(ctx);
for (auto it : order) {
if (DefinedRegular *sym = it.first->sym)
- config->order[sym->getName()] = it.second;
+ ctx.config.order[sym->getName()] = it.second;
}
}
- if (!config->order.empty())
+ if (!ctx.config.order.empty())
for (auto it : partialSections)
sortBySectionOrder(it.second->chunks);
}
OutputSection *&sec = sections[{name, outChars}];
if (!sec) {
sec = make<OutputSection>(name, outChars);
- outputSections.push_back(sec);
+ ctx.outputSections.push_back(sec);
}
return sec;
};
dtorsSec = createSection(".dtors", data | r | w);
// Then bin chunks by name and output characteristics.
- for (Chunk *c : symtab->getChunks()) {
+ for (Chunk *c : ctx.symtab.getChunks()) {
auto *sc = dyn_cast<SectionChunk>(c);
if (sc && !sc->live) {
- if (config->verbose)
+ if (ctx.config.verbose)
sc->printDiscardedMessage();
continue;
}
StringRef name = c->getSectionName();
- if (shouldStripSectionSuffix(sc, name))
+ if (shouldStripSectionSuffix(sc, name, ctx.config.mingw))
name = name.split('$').first;
if (name.startswith(".tls"))
// Move DISCARDABLE (or non-memory-mapped) sections to the end of file
// because the loader cannot handle holes. Stripping can remove other
// discardable ones than .reloc, which is first of them (created early).
- if (s->header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
+ if (s->header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE) {
+ // Move discardable sections named .debug_ to the end, after other
+ // discardable sections. Stripping only removes the sections named
+ // .debug_* - thus try to avoid leaving holes after stripping.
+ if (s->name.startswith(".debug_"))
+ return 3;
return 2;
+ }
// .rsrc should come at the end of the non-discardable sections because its
// size may change by the Win32 UpdateResources() function, causing
// subsequent sections to move (see https://crbug.com/827082).
return 1;
return 0;
};
- llvm::stable_sort(outputSections,
+ llvm::stable_sort(ctx.outputSections,
[&](const OutputSection *s, const OutputSection *t) {
return sectionOrder(s) < sectionOrder(t);
});
}
void Writer::createMiscChunks() {
- for (MergeChunk *p : MergeChunk::instances) {
+ Configuration *config = &ctx.config;
+
+ for (MergeChunk *p : ctx.mergeChunkInstances) {
if (p) {
p->finalizeContents();
rdataSec->addChunk(p);
}
// Create thunks for locally-dllimported symbols.
- if (!symtab->localImportChunks.empty()) {
- for (Chunk *c : symtab->localImportChunks)
+ if (!ctx.symtab.localImportChunks.empty()) {
+ for (Chunk *c : ctx.symtab.localImportChunks)
rdataSec->addChunk(c);
}
// Create Debug Information Chunks
OutputSection *debugInfoSec = config->mingw ? buildidSec : rdataSec;
if (config->debug || config->repro || config->cetCompat) {
- debugDirectory = make<DebugDirectoryChunk>(debugRecords, config->repro);
+ debugDirectory =
+ make<DebugDirectoryChunk>(ctx, debugRecords, config->repro);
debugDirectory->setAlignment(4);
debugInfoSec->addChunk(debugDirectory);
}
// output a PDB no matter what, and this chunk provides the only means of
// allowing a debugger to match a PDB and an executable. So we need it even
// if we're ultimately not going to write CodeView data to the PDB.
- buildId = make<CVDebugRecordChunk>();
+ buildId = make<CVDebugRecordChunk>(ctx);
debugRecords.push_back({COFF::IMAGE_DEBUG_TYPE_CODEVIEW, buildId});
}
// Initialize DLLOrder so that import entries are ordered in
// the same order as in the command line. (That affects DLL
// initialization order, and this ordering is MSVC-compatible.)
- for (ImportFile *file : ImportFile::instances) {
+ for (ImportFile *file : ctx.importFileInstances) {
if (!file->live)
continue;
std::string dll = StringRef(file->dllName).lower();
- if (config->dllOrder.count(dll) == 0)
- config->dllOrder[dll] = config->dllOrder.size();
+ if (ctx.config.dllOrder.count(dll) == 0)
+ ctx.config.dllOrder[dll] = ctx.config.dllOrder.size();
if (file->impSym && !isa<DefinedImportData>(file->impSym))
- fatal(toString(*file->impSym) + " was replaced");
+ fatal(toString(ctx, *file->impSym) + " was replaced");
DefinedImportData *impSym = cast_or_null<DefinedImportData>(file->impSym);
- if (config->delayLoads.count(StringRef(file->dllName).lower())) {
+ if (ctx.config.delayLoads.count(StringRef(file->dllName).lower())) {
if (!file->thunkSym)
fatal("cannot delay-load " + toString(file) +
- " due to import of data: " + toString(*impSym));
+ " due to import of data: " + toString(ctx, *impSym));
delayIdata.add(impSym);
} else {
idata.add(impSym);
}
void Writer::appendImportThunks() {
- if (ImportFile::instances.empty())
+ if (ctx.importFileInstances.empty())
return;
- for (ImportFile *file : ImportFile::instances) {
+ for (ImportFile *file : ctx.importFileInstances) {
if (!file->live)
continue;
continue;
if (!isa<DefinedImportThunk>(file->thunkSym))
- fatal(toString(*file->thunkSym) + " was replaced");
+ fatal(toString(ctx, *file->thunkSym) + " was replaced");
DefinedImportThunk *thunk = cast<DefinedImportThunk>(file->thunkSym);
if (file->thunkLive)
textSec->addChunk(thunk->getChunk());
}
if (!delayIdata.empty()) {
- Defined *helper = cast<Defined>(config->delayLoadHelper);
+ Defined *helper = cast<Defined>(ctx.config.delayLoadHelper);
delayIdata.create(helper);
for (Chunk *c : delayIdata.getChunks())
didatSec->addChunk(c);
dataSec->addChunk(c);
for (Chunk *c : delayIdata.getCodeChunks())
textSec->addChunk(c);
+ for (Chunk *c : delayIdata.getCodePData())
+ pdataSec->addChunk(c);
+ for (Chunk *c : delayIdata.getCodeUnwindInfo())
+ rdataSec->addChunk(c);
}
}
if (!edataSec->chunks.empty()) {
// Allow using a custom built export table from input object files, instead
// of having the linker synthesize the tables.
- if (config->hadExplicitExports)
+ if (ctx.config.hadExplicitExports)
warn("literal .edata sections override exports");
- } else if (!config->exports.empty()) {
+ } else if (!ctx.config.exports.empty()) {
for (Chunk *c : edata.chunks)
edataSec->addChunk(c);
}
edataEnd = edataSec->chunks.back();
}
// Warn on exported deleting destructor.
- for (auto e : config->exports)
+ for (auto e : ctx.config.exports)
if (e.sym && e.sym->getName().startswith("??_G"))
- warn("export of deleting dtor: " + toString(*e.sym));
+ warn("export of deleting dtor: " + toString(ctx, *e.sym));
}
void Writer::removeUnusedSections() {
// later. Only remove sections that have no Chunks at all.
return s->chunks.empty();
};
- outputSections.erase(
- std::remove_if(outputSections.begin(), outputSections.end(), isUnused),
- outputSections.end());
+ llvm::erase_if(ctx.outputSections, isUnused);
}
// The Windows loader doesn't seem to like empty sections,
// so we remove them if any.
void Writer::removeEmptySections() {
auto isEmpty = [](OutputSection *s) { return s->getVirtualSize() == 0; };
- outputSections.erase(
- std::remove_if(outputSections.begin(), outputSections.end(), isEmpty),
- outputSections.end());
+ llvm::erase_if(ctx.outputSections, isEmpty);
}
void Writer::assignOutputSectionIndices() {
// Assign final output section indices, and assign each chunk to its output
// section.
uint32_t idx = 1;
- for (OutputSection *os : outputSections) {
+ for (OutputSection *os : ctx.outputSections) {
os->sectionIndex = idx;
for (Chunk *c : os->chunks)
c->setOutputSectionIdx(idx);
// Merge chunks are containers of chunks, so assign those an output section
// too.
- for (MergeChunk *mc : MergeChunk::instances)
+ for (MergeChunk *mc : ctx.mergeChunkInstances)
if (mc)
for (SectionChunk *sc : mc->sections)
if (sc && sc->live)
return offsetOfEntry;
}
-Optional<coff_symbol16> Writer::createSymbol(Defined *def) {
+std::optional<coff_symbol16> Writer::createSymbol(Defined *def) {
coff_symbol16 sym;
switch (def->kind()) {
- case Symbol::DefinedAbsoluteKind:
- sym.Value = def->getRVA();
+ case Symbol::DefinedAbsoluteKind: {
+ auto *da = dyn_cast<DefinedAbsolute>(def);
+ // Note: COFF symbol can only store 32-bit values, so 64-bit absolute
+ // values will be truncated.
+ sym.Value = da->getVA();
sym.SectionNumber = IMAGE_SYM_ABSOLUTE;
break;
- case Symbol::DefinedSyntheticKind:
- // Relative symbols are unrepresentable in a COFF symbol table.
- return None;
+ }
default: {
// Don't write symbols that won't be written to the output to the symbol
// table.
+ // We also try to write DefinedSynthetic as a normal symbol. Some of these
+ // symbols do point to an actual chunk, like __safe_se_handler_table. Others
+ // like __ImageBase are outside of sections and thus cannot be represented.
Chunk *c = def->getChunk();
if (!c)
- return None;
- OutputSection *os = c->getOutputSection();
+ return std::nullopt;
+ OutputSection *os = ctx.getOutputSection(c);
if (!os)
- return None;
+ return std::nullopt;
sym.Value = def->getRVA() - os->getRVA();
sym.SectionNumber = os->sectionIndex;
// instead. Avoid emitting them to the symbol table, as they can confuse
// debuggers.
if (def->isRuntimePseudoReloc)
- return None;
+ return std::nullopt;
StringRef name = def->getName();
if (name.size() > COFF::NameSize) {
COFFSymbolRef ref = d->getCOFFSymbol();
sym.Type = ref.getType();
sym.StorageClass = ref.getStorageClass();
+ } else if (def->kind() == Symbol::DefinedImportThunkKind) {
+ sym.Type = (IMAGE_SYM_DTYPE_FUNCTION << SCT_COMPLEX_TYPE_SHIFT) |
+ IMAGE_SYM_TYPE_NULL;
+ sym.StorageClass = IMAGE_SYM_CLASS_EXTERNAL;
} else {
sym.Type = IMAGE_SYM_TYPE_NULL;
sym.StorageClass = IMAGE_SYM_CLASS_EXTERNAL;
// solution where discardable sections have long names preserved and
// non-discardable sections have their names truncated, to ensure that any
// section which is mapped at runtime also has its name mapped at runtime.
- for (OutputSection *sec : outputSections) {
+ for (OutputSection *sec : ctx.outputSections) {
if (sec->name.size() <= COFF::NameSize)
continue;
if ((sec->header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE) == 0)
continue;
- if (config->warnLongSectionNames) {
+ if (ctx.config.warnLongSectionNames) {
warn("section name " + sec->name +
" is longer than 8 characters and will use a non-standard string "
"table");
sec->setStringTableOff(addEntryToStringTable(sec->name));
}
- if (config->debugDwarf || config->debugSymtab) {
- for (ObjFile *file : ObjFile::instances) {
+ if (ctx.config.debugDwarf || ctx.config.debugSymtab) {
+ for (ObjFile *file : ctx.objFileInstances) {
for (Symbol *b : file->getSymbols()) {
auto *d = dyn_cast_or_null<Defined>(b);
if (!d || d->writtenToSymtab)
continue;
d->writtenToSymtab = true;
-
- if (Optional<coff_symbol16> sym = createSymbol(d))
+ if (auto *dc = dyn_cast_or_null<DefinedCOFF>(d)) {
+ COFFSymbolRef symRef = dc->getCOFFSymbol();
+ if (symRef.isSectionDefinition() ||
+ symRef.getStorageClass() == COFF::IMAGE_SYM_CLASS_LABEL)
+ continue;
+ }
+
+ if (std::optional<coff_symbol16> sym = createSymbol(d))
outputSymtab.push_back(*sym);
+
+ if (auto *dthunk = dyn_cast<DefinedImportThunk>(d)) {
+ if (!dthunk->wrappedSym->writtenToSymtab) {
+ dthunk->wrappedSym->writtenToSymtab = true;
+ if (std::optional<coff_symbol16> sym =
+ createSymbol(dthunk->wrappedSym))
+ outputSymtab.push_back(*sym);
+ }
+ }
}
}
}
pointerToSymbolTable = fileOff;
fileOff += outputSymtab.size() * sizeof(coff_symbol16);
fileOff += 4 + strtab.size();
- fileSize = alignTo(fileOff, config->fileAlign);
+ fileSize = alignTo(fileOff, ctx.config.fileAlign);
}
void Writer::mergeSections() {
lastPdata = pdataSec->chunks.back();
}
- for (auto &p : config->merge) {
+ for (auto &p : ctx.config.merge) {
StringRef toName = p.second;
if (p.first == toName)
continue;
StringSet<> names;
- while (1) {
+ while (true) {
if (!names.insert(toName).second)
fatal("/merge: cycle found for section '" + p.first + "'");
- auto i = config->merge.find(toName);
- if (i == config->merge.end())
+ auto i = ctx.config.merge.find(toName);
+ if (i == ctx.config.merge.end())
break;
toName = i->second;
}
// Visits all sections to assign incremental, non-overlapping RVAs and
// file offsets.
void Writer::assignAddresses() {
+ Configuration *config = &ctx.config;
+
sizeOfHeaders = dosStubSize + sizeof(PEMagic) + sizeof(coff_file_header) +
sizeof(data_directory) * numberOfDataDirectory +
- sizeof(coff_section) * outputSections.size();
+ sizeof(coff_section) * ctx.outputSections.size();
sizeOfHeaders +=
config->is64() ? sizeof(pe32plus_header) : sizeof(pe32_header);
sizeOfHeaders = alignTo(sizeOfHeaders, config->fileAlign);
// The first page is kept unmapped.
uint64_t rva = alignTo(sizeOfHeaders, config->align);
- for (OutputSection *sec : outputSections) {
+ for (OutputSection *sec : ctx.outputSections) {
if (sec == relocSec)
addBaserels();
uint64_t rawSize = 0, virtualSize = 0;
sizeOfImage = alignTo(rva, config->align);
// Assign addresses to sections in MergeChunks.
- for (MergeChunk *mc : MergeChunk::instances)
+ for (MergeChunk *mc : ctx.mergeChunkInstances)
if (mc)
mc->assignSubsectionRVAs();
}
// under DOS, that program gets run (usually to just print an error message).
// When run under Windows, the loader looks at AddressOfNewExeHeader and uses
// the PE header instead.
+ Configuration *config = &ctx.config;
uint8_t *buf = buffer->getBufferStart();
auto *dos = reinterpret_cast<dos_header *>(buf);
buf += sizeof(dos_header);
auto *coff = reinterpret_cast<coff_file_header *>(buf);
buf += sizeof(*coff);
coff->Machine = config->machine;
- coff->NumberOfSections = outputSections.size();
+ coff->NumberOfSections = ctx.outputSections.size();
coff->Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
if (config->largeAddressAware)
coff->Characteristics |= IMAGE_FILE_LARGE_ADDRESS_AWARE;
dir[BASE_RELOCATION_TABLE].RelativeVirtualAddress = relocSec->getRVA();
dir[BASE_RELOCATION_TABLE].Size = relocSec->getVirtualSize();
}
- if (Symbol *sym = symtab->findUnderscore("_tls_used")) {
+ if (Symbol *sym = ctx.symtab.findUnderscore("_tls_used")) {
if (Defined *b = dyn_cast<Defined>(sym)) {
dir[TLS_TABLE].RelativeVirtualAddress = b->getRVA();
dir[TLS_TABLE].Size = config->is64()
dir[DEBUG_DIRECTORY].RelativeVirtualAddress = debugDirectory->getRVA();
dir[DEBUG_DIRECTORY].Size = debugDirectory->getSize();
}
- if (Symbol *sym = symtab->findUnderscore("_load_config_used")) {
+ if (Symbol *sym = ctx.symtab.findUnderscore("_load_config_used")) {
if (auto *b = dyn_cast<DefinedRegular>(sym)) {
SectionChunk *sc = b->getChunk();
assert(b->getRVA() >= sc->getRVA());
}
// Write section table
- for (OutputSection *sec : outputSections) {
- sec->writeHeaderTo(buf);
+ for (OutputSection *sec : ctx.outputSections) {
+ sec->writeHeaderTo(buf, config->debug);
buf += sizeof(coff_section);
}
sectionTable = ArrayRef<uint8_t>(
- buf - outputSections.size() * sizeof(coff_section), buf);
+ buf - ctx.outputSections.size() * sizeof(coff_section), buf);
if (outputSymtab.empty() && strtab.empty())
return;
void Writer::createSEHTable() {
SymbolRVASet handlers;
- for (ObjFile *file : ObjFile::instances) {
+ for (ObjFile *file : ctx.objFileInstances) {
if (!file->hasSafeSEH())
error("/safeseh: " + file->getName() + " is not compatible with SEH");
markSymbolsForRVATable(file, file->getSXDataChunks(), handlers);
// Set the "no SEH" characteristic if there really were no handlers, or if
// there is no load config object to point to the table of handlers.
setNoSEHCharacteristic =
- handlers.empty() || !symtab->findUnderscore("_load_config_used");
+ handlers.empty() || !ctx.symtab.findUnderscore("_load_config_used");
maybeAddRVATable(std::move(handlers), "__safe_se_handler_table",
"__safe_se_handler_count");
// Visit all relocations from all section contributions of this object file and
// mark the relocation target as address-taken.
-static void markSymbolsWithRelocations(ObjFile *file,
- SymbolRVASet &usedSymbols) {
+void Writer::markSymbolsWithRelocations(ObjFile *file,
+ SymbolRVASet &usedSymbols) {
for (Chunk *c : file->getChunks()) {
// We only care about live section chunks. Common chunks and other chunks
// don't generally contain relocations.
continue;
for (const coff_relocation &reloc : sc->getRelocs()) {
- if (config->machine == I386 && reloc.Type == COFF::IMAGE_REL_I386_REL32)
+ if (ctx.config.machine == I386 &&
+ reloc.Type == COFF::IMAGE_REL_I386_REL32)
// Ignore relative relocations on x86. On x86_64 they can't be ignored
// since they're also used to compute absolute addresses.
continue;
// address-taken functions. It is sorted and uniqued, just like the safe SEH
// table.
void Writer::createGuardCFTables() {
+ Configuration *config = &ctx.config;
+
SymbolRVASet addressTakenSyms;
SymbolRVASet giatsRVASet;
std::vector<Symbol *> giatsSymbols;
SymbolRVASet longJmpTargets;
SymbolRVASet ehContTargets;
- for (ObjFile *file : ObjFile::instances) {
+ for (ObjFile *file : ctx.objFileInstances) {
// If the object was compiled with /guard:cf, the address taken symbols
// are in .gfids$y sections, the longjmp targets are in .gljmp$y sections,
// and ehcont targets are in .gehcont$y sections. If the object was not
// Set __guard_flags, which will be used in the load config to indicate that
// /guard:cf was enabled.
- uint32_t guardFlags = uint32_t(coff_guard_flags::CFInstrumented) |
- uint32_t(coff_guard_flags::HasFidTable);
+ uint32_t guardFlags = uint32_t(GuardFlags::CF_INSTRUMENTED) |
+ uint32_t(GuardFlags::CF_FUNCTION_TABLE_PRESENT);
if (config->guardCF & GuardCFLevel::LongJmp)
- guardFlags |= uint32_t(coff_guard_flags::HasLongJmpTable);
+ guardFlags |= uint32_t(GuardFlags::CF_LONGJUMP_TABLE_PRESENT);
if (config->guardCF & GuardCFLevel::EHCont)
- guardFlags |= uint32_t(coff_guard_flags::HasEHContTable);
- Symbol *flagSym = symtab->findUnderscore("__guard_flags");
+ guardFlags |= uint32_t(GuardFlags::EH_CONTINUATION_TABLE_PRESENT);
+ Symbol *flagSym = ctx.symtab.findUnderscore("__guard_flags");
cast<DefinedAbsolute>(flagSym)->setVA(guardFlags);
}
tableChunk = make<RVATableChunk>(std::move(tableSymbols));
rdataSec->addChunk(tableChunk);
- Symbol *t = symtab->findUnderscore(tableSym);
- Symbol *c = symtab->findUnderscore(countSym);
+ Symbol *t = ctx.symtab.findUnderscore(tableSym);
+ Symbol *c = ctx.symtab.findUnderscore(countSym);
replaceSymbol<DefinedSynthetic>(t, t->getName(), tableChunk);
cast<DefinedAbsolute>(c)->setVA(tableChunk->getSize() / (hasFlag ? 5 : 4));
}
void Writer::createRuntimePseudoRelocs() {
std::vector<RuntimePseudoReloc> rels;
- for (Chunk *c : symtab->getChunks()) {
+ for (Chunk *c : ctx.symtab.getChunks()) {
auto *sc = dyn_cast<SectionChunk>(c);
if (!sc || !sc->live)
continue;
sc->getRuntimePseudoRelocs(rels);
}
- if (!config->pseudoRelocs) {
+ if (!ctx.config.pseudoRelocs) {
// Not writing any pseudo relocs; if some were needed, error out and
// indicate what required them.
for (const RuntimePseudoReloc &rpr : rels)
EmptyChunk *endOfList = make<EmptyChunk>();
rdataSec->addChunk(endOfList);
- Symbol *headSym = symtab->findUnderscore("__RUNTIME_PSEUDO_RELOC_LIST__");
- Symbol *endSym = symtab->findUnderscore("__RUNTIME_PSEUDO_RELOC_LIST_END__");
+ Symbol *headSym = ctx.symtab.findUnderscore("__RUNTIME_PSEUDO_RELOC_LIST__");
+ Symbol *endSym =
+ ctx.symtab.findUnderscore("__RUNTIME_PSEUDO_RELOC_LIST_END__");
replaceSymbol<DefinedSynthetic>(headSym, headSym->getName(), table);
replaceSymbol<DefinedSynthetic>(endSym, endSym->getName(), endOfList);
}
// There's a symbol pointing to the start sentinel pointer, __CTOR_LIST__
// and __DTOR_LIST__ respectively.
void Writer::insertCtorDtorSymbols() {
- AbsolutePointerChunk *ctorListHead = make<AbsolutePointerChunk>(-1);
- AbsolutePointerChunk *ctorListEnd = make<AbsolutePointerChunk>(0);
- AbsolutePointerChunk *dtorListHead = make<AbsolutePointerChunk>(-1);
- AbsolutePointerChunk *dtorListEnd = make<AbsolutePointerChunk>(0);
+ AbsolutePointerChunk *ctorListHead = make<AbsolutePointerChunk>(ctx, -1);
+ AbsolutePointerChunk *ctorListEnd = make<AbsolutePointerChunk>(ctx, 0);
+ AbsolutePointerChunk *dtorListHead = make<AbsolutePointerChunk>(ctx, -1);
+ AbsolutePointerChunk *dtorListEnd = make<AbsolutePointerChunk>(ctx, 0);
ctorsSec->insertChunkAtStart(ctorListHead);
ctorsSec->addChunk(ctorListEnd);
dtorsSec->insertChunkAtStart(dtorListHead);
dtorsSec->addChunk(dtorListEnd);
- Symbol *ctorListSym = symtab->findUnderscore("__CTOR_LIST__");
- Symbol *dtorListSym = symtab->findUnderscore("__DTOR_LIST__");
+ Symbol *ctorListSym = ctx.symtab.findUnderscore("__CTOR_LIST__");
+ Symbol *dtorListSym = ctx.symtab.findUnderscore("__DTOR_LIST__");
replaceSymbol<DefinedSynthetic>(ctorListSym, ctorListSym->getName(),
ctorListHead);
replaceSymbol<DefinedSynthetic>(dtorListSym, dtorListSym->getName(),
// Handles /section options to allow users to overwrite
// section attributes.
void Writer::setSectionPermissions() {
- for (auto &p : config->section) {
+ for (auto &p : ctx.config.section) {
StringRef name = p.first;
uint32_t perm = p.second;
- for (OutputSection *sec : outputSections)
+ for (OutputSection *sec : ctx.outputSections)
if (sec->name == name)
sec->setPermissions(perm);
}
// Write section contents to a mmap'ed file.
void Writer::writeSections() {
- // Record the number of sections to apply section index relocations
- // against absolute symbols. See applySecIdx in Chunks.cpp..
- DefinedAbsolute::numOutputSections = outputSections.size();
-
uint8_t *buf = buffer->getBufferStart();
- for (OutputSection *sec : outputSections) {
+ for (OutputSection *sec : ctx.outputSections) {
uint8_t *secBuf = buf + sec->getFileOff();
// Fill gaps between functions in .text with INT3 instructions
// instead of leaving as NUL bytes (which can be interpreted as
// 2) In all cases, the PE COFF file header also contains a timestamp.
// For reproducibility, instead of a timestamp we want to use a hash of the
// PE contents.
+ Configuration *config = &ctx.config;
+
if (config->debug) {
assert(buildId && "BuildId is not set!");
// BuildId->BuildId was filled in when the PDB was written.
return;
// We assume .pdata contains function table entries only.
auto bufAddr = [&](Chunk *c) {
- OutputSection *os = c->getOutputSection();
+ OutputSection *os = ctx.getOutputSection(c);
return buffer->getBufferStart() + os->getFileOff() + c->getRVA() -
os->getRVA();
};
uint8_t *begin = bufAddr(firstPdata);
uint8_t *end = bufAddr(lastPdata) + lastPdata->getSize();
- if (config->machine == AMD64) {
+ if (ctx.config.machine == AMD64) {
struct Entry { ulittle32_t begin, end, unwind; };
if ((end - begin) % sizeof(Entry) != 0) {
fatal("unexpected .pdata size: " + Twine(end - begin) +
[](const Entry &a, const Entry &b) { return a.begin < b.begin; });
return;
}
- if (config->machine == ARMNT || config->machine == ARM64) {
+ if (ctx.config.machine == ARMNT || ctx.config.machine == ARM64) {
struct Entry { ulittle32_t begin, unwind; };
if ((end - begin) % sizeof(Entry) != 0) {
fatal("unexpected .pdata size: " + Twine(end - begin) +
};
llvm::stable_sort(chunks, sectionChunkOrder);
- if (config->verbose) {
+ if (ctx.config.verbose) {
for (auto &c : chunks) {
auto sc = dyn_cast<SectionChunk>(c);
log(" " + sc->file->mb.getBufferIdentifier().str() +
}
OutputSection *Writer::findSection(StringRef name) {
- for (OutputSection *sec : outputSections)
+ for (OutputSection *sec : ctx.outputSections)
if (sec->name == name)
return sec;
return nullptr;
uint32_t Writer::getSizeOfInitializedData() {
uint32_t res = 0;
- for (OutputSection *s : outputSections)
+ for (OutputSection *s : ctx.outputSections)
if (s->header.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA)
res += s->getRawSize();
return res;
// Add base relocations to .reloc section.
void Writer::addBaserels() {
- if (!config->relocatable)
+ if (!ctx.config.relocatable)
return;
relocSec->chunks.clear();
std::vector<Baserel> v;
- for (OutputSection *sec : outputSections) {
+ for (OutputSection *sec : ctx.outputSections) {
if (sec->header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
continue;
// Collect all locations for base relocations.
void Writer::fixTlsAlignment() {
Defined *tlsSym =
- dyn_cast_or_null<Defined>(symtab->findUnderscore("_tls_used"));
+ dyn_cast_or_null<Defined>(ctx.symtab.findUnderscore("_tls_used"));
if (!tlsSym)
return;
- OutputSection *sec = tlsSym->getChunk()->getOutputSection();
+ OutputSection *sec = ctx.getOutputSection(tlsSym->getChunk());
assert(sec && tlsSym->getRVA() >= sec->getRVA() &&
"no output section for _tls_used");
uint8_t *secBuf = buffer->getBufferStart() + sec->getFileOff();
uint64_t tlsOffset = tlsSym->getRVA() - sec->getRVA();
- uint64_t directorySize = config->is64()
+ uint64_t directorySize = ctx.config.is64()
? sizeof(object::coff_tls_directory64)
: sizeof(object::coff_tls_directory32);
if (tlsOffset + directorySize > sec->getRawSize())
fatal("_tls_used sym is malformed");
- if (config->is64()) {
+ if (ctx.config.is64()) {
object::coff_tls_directory64 *tlsDir =
reinterpret_cast<object::coff_tls_directory64 *>(&secBuf[tlsOffset]);
tlsDir->setAlignment(tlsAlignment);
tlsDir->setAlignment(tlsAlignment);
}
}
+
+void Writer::checkLoadConfig() {
+ Symbol *sym = ctx.symtab.findUnderscore("_load_config_used");
+ auto *b = cast_if_present<DefinedRegular>(sym);
+ if (!b) {
+ if (ctx.config.guardCF != GuardCFLevel::Off)
+ warn("Control Flow Guard is enabled but '_load_config_used' is missing");
+ return;
+ }
+
+ OutputSection *sec = ctx.getOutputSection(b->getChunk());
+ uint8_t *buf = buffer->getBufferStart();
+ uint8_t *secBuf = buf + sec->getFileOff();
+ uint8_t *symBuf = secBuf + (b->getRVA() - sec->getRVA());
+ uint32_t expectedAlign = ctx.config.is64() ? 8 : 4;
+ if (b->getChunk()->getAlignment() < expectedAlign)
+ warn("'_load_config_used' is misaligned (expected alignment to be " +
+ Twine(expectedAlign) + " bytes, got " +
+ Twine(b->getChunk()->getAlignment()) + " instead)");
+ else if (!isAligned(Align(expectedAlign), b->getRVA()))
+ warn("'_load_config_used' is misaligned (RVA is 0x" +
+ Twine::utohexstr(b->getRVA()) + " not aligned to " +
+ Twine(expectedAlign) + " bytes)");
+
+ if (ctx.config.is64())
+ checkLoadConfigGuardData(
+ reinterpret_cast<const coff_load_configuration64 *>(symBuf));
+ else
+ checkLoadConfigGuardData(
+ reinterpret_cast<const coff_load_configuration32 *>(symBuf));
+}
+
+template <typename T>
+void Writer::checkLoadConfigGuardData(const T *loadConfig) {
+ size_t loadConfigSize = loadConfig->Size;
+
+#define RETURN_IF_NOT_CONTAINS(field) \
+ if (loadConfigSize < offsetof(T, field) + sizeof(T::field)) { \
+ warn("'_load_config_used' structure too small to include " #field); \
+ return; \
+ }
+
+#define IF_CONTAINS(field) \
+ if (loadConfigSize >= offsetof(T, field) + sizeof(T::field))
+
+#define CHECK_VA(field, sym) \
+ if (auto *s = dyn_cast<DefinedSynthetic>(ctx.symtab.findUnderscore(sym))) \
+ if (loadConfig->field != ctx.config.imageBase + s->getRVA()) \
+ warn(#field " not set correctly in '_load_config_used'");
+
+#define CHECK_ABSOLUTE(field, sym) \
+ if (auto *s = dyn_cast<DefinedAbsolute>(ctx.symtab.findUnderscore(sym))) \
+ if (loadConfig->field != s->getVA()) \
+ warn(#field " not set correctly in '_load_config_used'");
+
+ if (ctx.config.guardCF == GuardCFLevel::Off)
+ return;
+ RETURN_IF_NOT_CONTAINS(GuardFlags)
+ CHECK_VA(GuardCFFunctionTable, "__guard_fids_table")
+ CHECK_ABSOLUTE(GuardCFFunctionCount, "__guard_fids_count")
+ CHECK_ABSOLUTE(GuardFlags, "__guard_flags")
+ IF_CONTAINS(GuardAddressTakenIatEntryCount) {
+ CHECK_VA(GuardAddressTakenIatEntryTable, "__guard_iat_table")
+ CHECK_ABSOLUTE(GuardAddressTakenIatEntryCount, "__guard_iat_count")
+ }
+
+ if (!(ctx.config.guardCF & GuardCFLevel::LongJmp))
+ return;
+ RETURN_IF_NOT_CONTAINS(GuardLongJumpTargetCount)
+ CHECK_VA(GuardLongJumpTargetTable, "__guard_longjmp_table")
+ CHECK_ABSOLUTE(GuardLongJumpTargetCount, "__guard_longjmp_count")
+
+ if (!(ctx.config.guardCF & GuardCFLevel::EHCont))
+ return;
+ RETURN_IF_NOT_CONTAINS(GuardEHContinuationCount)
+ CHECK_VA(GuardEHContinuationTable, "__guard_eh_cont_table")
+ CHECK_ABSOLUTE(GuardEHContinuationCount, "__guard_eh_cont_count")
+
+#undef RETURN_IF_NOT_CONTAINS
+#undef IF_CONTAINS
+#undef CHECK_VA
+#undef CHECK_ABSOLUTE
+}
#include <cstdint>
#include <vector>
-namespace lld {
-namespace coff {
+namespace lld::coff {
static const int pageSize = 4096;
+class COFFLinkerContext;
-void writeResult();
+void writeResult(COFFLinkerContext &ctx);
class PartialSection {
public:
void insertChunkAtStart(Chunk *c);
void merge(OutputSection *other);
void setPermissions(uint32_t c);
- uint64_t getRVA() { return header.VirtualAddress; }
- uint64_t getFileOff() { return header.PointerToRawData; }
- void writeHeaderTo(uint8_t *buf);
+ uint64_t getRVA() const { return header.VirtualAddress; }
+ uint64_t getFileOff() const { return header.PointerToRawData; }
+ void writeHeaderTo(uint8_t *buf, bool isDebug);
void addContributingPartialSection(PartialSection *sec);
- // Clear the output sections static container.
- static void clear();
-
// Returns the size of this section in an executable memory image.
// This may be smaller than the raw size (the raw size is multiple
// of disk sector size, so there may be padding at end), or may be
uint32_t stringTableOff = 0;
};
-} // namespace coff
-} // namespace lld
+} // namespace lld::coff
#endif
return ::getInteger(args, key, Default, 16);
}
-std::vector<StringRef> lld::args::getStrings(opt::InputArgList &args, int id) {
- std::vector<StringRef> v;
+SmallVector<StringRef, 0> lld::args::getStrings(opt::InputArgList &args,
+ int id) {
+ SmallVector<StringRef, 0> v;
for (auto *arg : args.filtered(id))
v.push_back(arg->getValue());
return v;
-set(LLD_SYSTEM_LIBS ${LLVM_PTHREAD_LIB})
-
-if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
- list(APPEND LLD_SYSTEM_LIBS atomic)
-endif()
-
find_first_existing_vc_file("${LLVM_MAIN_SRC_DIR}" llvm_vc)
find_first_existing_vc_file("${LLD_SOURCE_DIR}" lld_vc)
set(version_inc "${CMAKE_CURRENT_BINARY_DIR}/VCSVersion.inc")
-set(generate_vcs_version_script "${LLVM_CMAKE_PATH}/GenerateVersionFromVCS.cmake")
+set(generate_vcs_version_script "${LLVM_CMAKE_DIR}/GenerateVersionFromVCS.cmake")
if(lld_vc AND LLVM_APPEND_VC_REV)
set(lld_source_dir ${LLD_SOURCE_DIR})
add_lld_library(lldCommon
Args.cpp
+ CommonLinkerContext.cpp
DWARF.cpp
ErrorHandler.cpp
Filesystem.cpp
Option
Support
Target
+ TargetParser
LINK_LIBS
- ${LLD_SYSTEM_LIBS}
+ ${LLVM_PTHREAD_LIB}
+ ${LLVM_ATOMIC_LIB}
DEPENDS
intrinsics_gen
--- /dev/null
+//===- CommonLinkerContext.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 "lld/Common/CommonLinkerContext.h"
+#include "lld/Common/ErrorHandler.h"
+#include "lld/Common/Memory.h"
+
+#include "llvm/CodeGen/CommandFlags.h"
+
+using namespace llvm;
+using namespace lld;
+
+// Reference to the current LLD instance. This is a temporary situation, until
+// we pass this context everywhere by reference, or we make it a thread_local,
+// as in https://reviews.llvm.org/D108850?id=370678 where each thread can be
+// associated with a LLD instance. Only then will LLD be free of global
+// state.
+static CommonLinkerContext *lctx;
+
+CommonLinkerContext::CommonLinkerContext() {
+ lctx = this;
+ // Fire off the static initializations in CGF's constructor.
+ codegen::RegisterCodeGenFlags CGF;
+}
+
+CommonLinkerContext::~CommonLinkerContext() {
+ assert(lctx);
+ // Explicitly call the destructors since we created the objects with placement
+ // new in SpecificAlloc::create().
+ for (auto &it : instances)
+ it.second->~SpecificAllocBase();
+ lctx = nullptr;
+}
+
+CommonLinkerContext &lld::commonContext() {
+ assert(lctx);
+ return *lctx;
+}
+
+bool lld::hasContext() { return lctx != nullptr; }
+
+void CommonLinkerContext::destroy() {
+ if (lctx == nullptr)
+ return;
+ delete lctx;
+}
// Returns the pair of file name and line number describing location of data
// object (variable, array, etc) definition.
-Optional<std::pair<std::string, unsigned>>
+std::optional<std::pair<std::string, unsigned>>
DWARFCache::getVariableLoc(StringRef name) {
// Return if we have no debug information about data object.
auto it = variableLoc.find(name);
if (it == variableLoc.end())
- return None;
+ return std::nullopt;
// Take file name string from line table.
std::string fileName;
if (!it->second.lt->getFileNameByIndex(
it->second.file, {},
DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, fileName))
- return None;
+ return std::nullopt;
return std::make_pair(fileName, it->second.line);
}
// Returns source line information for a given offset
// using DWARF debug info.
-Optional<DILineInfo> DWARFCache::getDILineInfo(uint64_t offset,
- uint64_t sectionIndex) {
+std::optional<DILineInfo> DWARFCache::getDILineInfo(uint64_t offset,
+ uint64_t sectionIndex) {
DILineInfo info;
for (const llvm::DWARFDebugLine::LineTable *lt : lineTables) {
if (lt->getFileLineInfoForAddress(
DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath, info))
return info;
}
- return None;
+ return std::nullopt;
}
} // namespace lld
#include "llvm/Support/Parallel.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "llvm/ADT/Twine.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/DiagnosticPrinter.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/raw_ostream.h"
-#include <mutex>
#include <regex>
using namespace llvm;
using namespace lld;
-// The functions defined in this file can be called from multiple threads,
-// but lld::outs() or lld::errs() are not thread-safe. We protect them using a
-// mutex.
-static std::mutex mu;
-
-// We want to separate multi-line messages with a newline. `sep` is "\n"
-// if the last messages was multi-line. Otherwise "".
-static StringRef sep;
-
static StringRef getSeparator(const Twine &msg) {
if (StringRef(msg.str()).contains('\n'))
return "\n";
return "";
}
-raw_ostream *lld::stdoutOS;
-raw_ostream *lld::stderrOS;
+ErrorHandler::~ErrorHandler() {
+ if (cleanupCallback)
+ cleanupCallback();
+}
+
+void ErrorHandler::initialize(llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly,
+ bool disableOutput) {
+ this->stdoutOS = &stdoutOS;
+ this->stderrOS = &stderrOS;
+ stderrOS.enable_colors(stderrOS.has_colors());
+ this->exitEarly = exitEarly;
+ this->disableOutput = disableOutput;
+}
+
+void ErrorHandler::flushStreams() {
+ std::lock_guard<std::mutex> lock(mu);
+ outs().flush();
+ errs().flush();
+}
+
+ErrorHandler &lld::errorHandler() { return context().e; }
-ErrorHandler &lld::errorHandler() {
- static ErrorHandler handler;
- return handler;
+void lld::error(const Twine &msg) { errorHandler().error(msg); }
+void lld::error(const Twine &msg, ErrorTag tag, ArrayRef<StringRef> args) {
+ errorHandler().error(msg, tag, args);
+}
+void lld::fatal(const Twine &msg) { errorHandler().fatal(msg); }
+void lld::log(const Twine &msg) { errorHandler().log(msg); }
+void lld::message(const Twine &msg, llvm::raw_ostream &s) {
+ errorHandler().message(msg, s);
}
+void lld::warn(const Twine &msg) { errorHandler().warn(msg); }
+uint64_t lld::errorCount() { return errorHandler().errorCount; }
raw_ostream &lld::outs() {
- if (errorHandler().disableOutput)
+ ErrorHandler &e = errorHandler();
+ return e.outs();
+}
+
+raw_ostream &lld::errs() {
+ ErrorHandler &e = errorHandler();
+ return e.errs();
+}
+
+raw_ostream &ErrorHandler::outs() {
+ if (disableOutput)
return llvm::nulls();
return stdoutOS ? *stdoutOS : llvm::outs();
}
-raw_ostream &lld::errs() {
- if (errorHandler().disableOutput)
+raw_ostream &ErrorHandler::errs() {
+ if (disableOutput)
return llvm::nulls();
return stderrOS ? *stderrOS : llvm::errs();
}
void lld::exitLld(int val) {
- // Delete any temporary file, while keeping the memory mapping open.
- if (errorHandler().outputBuffer)
- errorHandler().outputBuffer->discard();
+ if (hasContext()) {
+ ErrorHandler &e = errorHandler();
+ // Delete any temporary file, while keeping the memory mapping open.
+ if (e.outputBuffer)
+ e.outputBuffer->discard();
+ }
// Re-throw a possible signal or exception once/if it was catched by
// safeLldMain().
if (!CrashRecoveryContext::GetCurrent())
llvm_shutdown();
- {
- std::lock_guard<std::mutex> lock(mu);
- lld::outs().flush();
- lld::errs().flush();
- }
+ if (hasContext())
+ lld::errorHandler().flushStreams();
+
// When running inside safeLldMain(), restore the control flow back to the
// CrashRecoveryContext. Otherwise simply use _exit(), meanning no cleanup,
// since we want to avoid further crashes on shutdown.
SmallString<128> s;
raw_svector_ostream os(s);
DiagnosticPrinterRawOStream dp(os);
+
+ // For an inline asm diagnostic, prepend the module name to get something like
+ // "$module <inline asm>:1:5: ".
+ if (auto *dism = dyn_cast<DiagnosticInfoSrcMgr>(&di))
+ if (dism->isInlineAsmDiag())
+ os << dism->getModuleName() << ' ';
+
di.print(dp);
switch (di.getSeverity()) {
case DS_Error:
return std::string(logName);
}
+void ErrorHandler::reportDiagnostic(StringRef location, Colors c,
+ StringRef diagKind, const Twine &msg) {
+ SmallString<256> buf;
+ raw_svector_ostream os(buf);
+ os << sep << location << ": ";
+ if (!diagKind.empty()) {
+ if (lld::errs().colors_enabled()) {
+ os.enable_colors(true);
+ os << c << diagKind << ": " << Colors::RESET;
+ } else {
+ os << diagKind << ": ";
+ }
+ }
+ os << msg << '\n';
+ lld::errs() << buf;
+}
+
void ErrorHandler::log(const Twine &msg) {
if (!verbose || disableOutput)
return;
std::lock_guard<std::mutex> lock(mu);
- lld::errs() << logName << ": " << msg << "\n";
+ reportDiagnostic(logName, Colors::RESET, "", msg);
}
-void ErrorHandler::message(const Twine &msg) {
+void ErrorHandler::message(const Twine &msg, llvm::raw_ostream &s) {
if (disableOutput)
return;
std::lock_guard<std::mutex> lock(mu);
- lld::outs() << msg << "\n";
- lld::outs().flush();
+ s << msg << "\n";
+ s.flush();
}
void ErrorHandler::warn(const Twine &msg) {
return;
}
+ if (suppressWarnings)
+ return;
+
std::lock_guard<std::mutex> lock(mu);
- lld::errs() << sep << getLocation(msg) << ": " << Colors::MAGENTA
- << "warning: " << Colors::RESET << msg << "\n";
+ reportDiagnostic(getLocation(msg), Colors::MAGENTA, "warning", msg);
sep = getSeparator(msg);
}
std::lock_guard<std::mutex> lock(mu);
if (errorLimit == 0 || errorCount < errorLimit) {
- lld::errs() << sep << getLocation(msg) << ": " << Colors::RED
- << "error: " << Colors::RESET << msg << "\n";
+ reportDiagnostic(getLocation(msg), Colors::RED, "error", msg);
} else if (errorCount == errorLimit) {
- lld::errs() << sep << getLocation(msg) << ": " << Colors::RED
- << "error: " << Colors::RESET << errorLimitExceededMsg
- << "\n";
+ reportDiagnostic(logName, Colors::RED, "error", errorLimitExceededMsg);
exit = exitEarly;
}
//===----------------------------------------------------------------------===//
#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
using namespace llvm;
using namespace lld;
-BumpPtrAllocator lld::bAlloc;
-StringSaver lld::saver{bAlloc};
-std::vector<SpecificAllocBase *> lld::SpecificAllocBase::instances;
-
-void lld::freeArena() {
- for (SpecificAllocBase *alloc : SpecificAllocBase::instances)
- alloc->reset();
- bAlloc.Reset();
+SpecificAllocBase *
+lld::SpecificAllocBase::getOrCreate(void *tag, size_t size, size_t align,
+ SpecificAllocBase *(&creator)(void *)) {
+ auto &instances = context().instances;
+ auto &instance = instances[tag];
+ if (instance == nullptr) {
+ void *storage = context().bAlloc.Allocate(size, align);
+ instance = creator(storage);
+ }
+ return instance;
}
#include "lld/Common/Strings.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/LLVM.h"
-#include "llvm/Demangle/Demangle.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/GlobPattern.h"
#include <algorithm>
using namespace llvm;
using namespace lld;
-// Returns the demangled C++ symbol name for name.
-std::string lld::demangleItanium(StringRef name) {
- // demangleItanium() can be called for all symbols. Only demangle C++ symbols,
- // to avoid getting unexpected result for a C symbol that happens to match a
- // mangled type name such as "Pi" (which would demangle to "int*").
- if (!name.startswith("_Z") && !name.startswith("__Z") &&
- !name.startswith("___Z") && !name.startswith("____Z"))
- return std::string(name);
-
- return demangle(std::string(name));
-}
-
SingleStringMatcher::SingleStringMatcher(StringRef Pattern) {
if (Pattern.size() > 2 && Pattern.startswith("\"") &&
Pattern.endswith("\"")) {
}
// Converts a hex string (e.g. "deadbeef") to a vector.
-std::vector<uint8_t> lld::parseHex(StringRef s) {
- std::vector<uint8_t> hex;
+SmallVector<uint8_t, 0> lld::parseHex(StringRef s) {
+ SmallVector<uint8_t, 0> hex;
while (!s.empty()) {
StringRef b = s.substr(0, 2);
s = s.substr(2);
//===----------------------------------------------------------------------===//
#include "lld/Common/TargetOptionsCommandFlags.h"
-
+#include "llvm/ADT/Triple.h"
#include "llvm/CodeGen/CommandFlags.h"
#include "llvm/Target/TargetOptions.h"
-
-static llvm::codegen::RegisterCodeGenFlags CGF;
+#include <optional>
llvm::TargetOptions lld::initTargetOptionsFromCodeGenFlags() {
return llvm::codegen::InitTargetOptionsFromCodeGenFlags(llvm::Triple());
}
-llvm::Optional<llvm::Reloc::Model> lld::getRelocModelFromCMModel() {
+std::optional<llvm::Reloc::Model> lld::getRelocModelFromCMModel() {
return llvm::codegen::getExplicitRelocModel();
}
-llvm::Optional<llvm::CodeModel::Model> lld::getCodeModelFromCMModel() {
+std::optional<llvm::CodeModel::Model> lld::getCodeModelFromCMModel() {
return llvm::codegen::getExplicitCodeModel();
}
#include "lld/Common/Timer.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/Support/Format.h"
+#include <ratio>
using namespace lld;
using namespace llvm;
ScopedTimer::~ScopedTimer() { stop(); }
-Timer::Timer(llvm::StringRef name) : name(std::string(name)) {}
-Timer::Timer(llvm::StringRef name, Timer &parent) : name(std::string(name)) {
+Timer::Timer(llvm::StringRef name) : total(0), name(std::string(name)) {}
+Timer::Timer(llvm::StringRef name, Timer &parent)
+ : total(0), name(std::string(name)) {
parent.children.push_back(this);
}
-Timer &Timer::root() {
- static Timer rootTimer("Total Link Time");
- return rootTimer;
-}
-
void Timer::print() {
- double totalDuration = static_cast<double>(root().millis());
+ double totalDuration = static_cast<double>(millis());
// We want to print the grand total under all the intermediate phases, so we
// print all children first, then print the total under that.
message(std::string(50, '-'));
- root().print(0, root().millis(), false);
+ print(0, millis(), false);
}
double Timer::millis() const {
#include "VCSVersion.inc"
// Returns a version string, e.g.:
-// lld 9.0.0 (https://github.com/llvm/llvm-project.git 9efdd7ac5e914d3c9fa1ef)
+// LLD 14.0.0 (https://github.com/llvm/llvm-project.git
+// 2d9759c7902c5cbc9a7e3ab623321d5578d51687)
std::string lld::getLLDVersion() {
#ifdef LLD_VENDOR
#define LLD_VENDOR_DISPLAY LLD_VENDOR " "
#else
#define LLD_VENDOR_DISPLAY
#endif
-#if defined(LLD_REPOSITORY) && defined(LLD_REVISION)
- return LLD_VENDOR_DISPLAY "LLD " LLD_VERSION_STRING " (" LLD_REPOSITORY
- " " LLD_REVISION ")";
-#else
return LLD_VENDOR_DISPLAY "LLD " LLD_VERSION_STRING;
-#endif
#undef LLD_VENDOR_DISPLAY
}
//===----------------------------------------------------------------------===//
#include "AArch64ErrataFix.h"
-#include "Config.h"
+#include "InputFiles.h"
#include "LinkerScript.h"
#include "OutputSections.h"
#include "Relocations.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Strings.h"
#include "llvm/Support/Endian.h"
-#include "llvm/Support/raw_ostream.h"
#include <algorithm>
using namespace llvm;
return (instr & 0x9f000000) == 0x90000000;
}
-// Load and store bit patterns from ARMv8-A ARM ARM.
+// Load and store bit patterns from ARMv8-A.
// Instructions appear in order of appearance starting from table in
// C4.1.3 Loads and Stores.
}
uint64_t patchOff = 0;
- const uint8_t *buf = isec->data().begin();
+ const uint8_t *buf = isec->content().begin();
const ulittle32_t *instBuf = reinterpret_cast<const ulittle32_t *>(buf + off);
uint32_t instr1 = *instBuf++;
uint32_t instr2 = *instBuf++;
return patchOff;
}
-class elf::Patch843419Section : public SyntheticSection {
+class elf::Patch843419Section final : public SyntheticSection {
public:
Patch843419Section(InputSection *p, uint64_t off);
patchee(p), patcheeOffset(off) {
this->parent = p->getParent();
patchSym = addSyntheticLocal(
- saver.save("__CortexA53843419_" + utohexstr(getLDSTAddr())), STT_FUNC, 0,
- getSize(), *this);
- addSyntheticLocal(saver.save("$x"), STT_NOTYPE, 0, 0, *this);
+ saver().save("__CortexA53843419_" + utohexstr(getLDSTAddr())), STT_FUNC,
+ 0, getSize(), *this);
+ addSyntheticLocal(saver().save("$x"), STT_NOTYPE, 0, 0, *this);
}
uint64_t Patch843419Section::getLDSTAddr() const {
void Patch843419Section::writeTo(uint8_t *buf) {
// Copy the instruction that we will be replacing with a branch in the
// patchee Section.
- write32le(buf, read32le(patchee->data().begin() + patcheeOffset));
+ write32le(buf, read32le(patchee->content().begin() + patcheeOffset));
// Apply any relocation transferred from the original patchee section.
- relocateAlloc(buf, buf + getSize());
+ target->relocateAlloc(*this, buf);
// Return address is the next instruction after the one we have just copied.
uint64_t s = getLDSTAddr() + 4;
};
// Collect mapping symbols for every executable InputSection.
- for (InputFile *file : objectFiles) {
- auto *f = cast<ObjFile<ELF64LE>>(file);
- for (Symbol *b : f->getLocalSymbols()) {
+ for (ELFFileBase *file : ctx.objectFiles) {
+ for (Symbol *b : file->getLocalSymbols()) {
auto *def = dyn_cast<Defined>(b);
if (!def)
continue;
// determine the insertion point. This is ok as we only merge into an
// InputSectionDescription once per pass, and at the end of the pass
// assignAddresses() will recalculate all the outSecOff values.
- std::vector<InputSection *> tmp;
+ SmallVector<InputSection *, 0> tmp;
tmp.reserve(isd.sections.size() + patches.size());
auto mergeCmp = [](const InputSection *a, const InputSection *b) {
if (a->outSecOff != b->outSecOff)
// and replace the relocation with a R_AARCH_JUMP26 branch relocation.
// Case 4: No relocation. We must create a new R_AARCH64_JUMP26 branch
// relocation at the offset.
- auto relIt = llvm::find_if(isec->relocations, [=](const Relocation &r) {
+ auto relIt = llvm::find_if(isec->relocs(), [=](const Relocation &r) {
return r.offset == patcheeOffset;
});
- if (relIt != isec->relocations.end() &&
+ if (relIt != isec->relocs().end() &&
(relIt->type == R_AARCH64_JUMP26 || relIt->expr == R_RELAX_TLS_IE_TO_LE))
return;
return Relocation{R_PC, R_AARCH64_JUMP26, offset, 0, patchSym};
};
- if (relIt != isec->relocations.end()) {
- ps->relocations.push_back(
- {relIt->expr, relIt->type, 0, relIt->addend, relIt->sym});
+ if (relIt != isec->relocs().end()) {
+ ps->addReloc({relIt->expr, relIt->type, 0, relIt->addend, relIt->sym});
*relIt = makeRelToPatch(patcheeOffset, ps->patchSym);
} else
- isec->relocations.push_back(makeRelToPatch(patcheeOffset, ps->patchSym));
+ isec->addReloc(makeRelToPatch(patcheeOffset, ps->patchSym));
}
// Scan all the instructions in InputSectionDescription, for each instance of
while (codeSym != mapSyms.end()) {
auto dataSym = std::next(codeSym);
uint64_t off = (*codeSym)->value;
- uint64_t limit =
- (dataSym == mapSyms.end()) ? isec->data().size() : (*dataSym)->value;
+ uint64_t limit = (dataSym == mapSyms.end()) ? isec->content().size()
+ : (*dataSym)->value;
while (off < limit) {
uint64_t startAddr = isec->getVA(off);
for (OutputSection *os : outputSections) {
if (!(os->flags & SHF_ALLOC) || !(os->flags & SHF_EXECINSTR))
continue;
- for (BaseCommand *bc : os->sectionCommands)
- if (auto *isd = dyn_cast<InputSectionDescription>(bc)) {
+ for (SectionCommand *cmd : os->commands)
+ if (auto *isd = dyn_cast<InputSectionDescription>(cmd)) {
std::vector<Patch843419Section *> patches =
patchInputSectionDescription(*isd);
if (!patches.empty()) {
#define LLD_ELF_AARCH64ERRATAFIX_H
#include "lld/Common/LLVM.h"
-#include <map>
+#include "llvm/ADT/DenseMap.h"
#include <vector>
-namespace lld {
-namespace elf {
+namespace lld::elf {
class Defined;
class InputSection;
class InputSectionDescription;
-class OutputSection;
class Patch843419Section;
class AArch64Err843419Patcher {
// A cache of the mapping symbols defined by the InputSection sorted in order
// of ascending value with redundant symbols removed. These describe
// the ranges of code and data in an executable InputSection.
- std::map<InputSection *, std::vector<const Defined *>> sectionMap;
+ llvm::DenseMap<InputSection *, std::vector<const Defined *>> sectionMap;
bool initialized = false;
};
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
//===----------------------------------------------------------------------===//
#include "ARMErrataFix.h"
-
-#include "Config.h"
+#include "InputFiles.h"
#include "LinkerScript.h"
#include "OutputSections.h"
#include "Relocations.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Strings.h"
#include "llvm/Support/Endian.h"
-#include "llvm/Support/raw_ostream.h"
#include <algorithm>
using namespace llvm;
// 00001002 2 - bytes padding
// 00001004 __CortexA8657417_00000FFE: B.w func
-class elf::Patch657417Section : public SyntheticSection {
+class elf::Patch657417Section final : public SyntheticSection {
public:
Patch657417Section(InputSection *p, uint64_t off, uint32_t instr, bool isARM);
patchee(p), patcheeOffset(off), instr(instr), isARM(isARM) {
parent = p->getParent();
patchSym = addSyntheticLocal(
- saver.save("__CortexA8657417_" + utohexstr(getBranchAddr())), STT_FUNC,
+ saver().save("__CortexA8657417_" + utohexstr(getBranchAddr())), STT_FUNC,
isARM ? 0 : 1, getSize(), *this);
- addSyntheticLocal(saver.save(isARM ? "$a" : "$t"), STT_NOTYPE, 0, 0, *this);
+ addSyntheticLocal(saver().save(isARM ? "$a" : "$t"), STT_NOTYPE, 0, 0, *this);
}
uint64_t Patch657417Section::getBranchAddr() const {
else
write32le(buf, 0x9000f000);
// If we have a relocation then apply it.
- if (!relocations.empty()) {
- relocateAlloc(buf, buf + getSize());
+ if (!relocs().empty()) {
+ target->relocateAlloc(*this, buf);
return;
}
uint32_t instr, const Relocation *r) {
uint64_t sourceAddr = isec->getVA(0) + off;
assert((sourceAddr & 0xfff) == 0xffe);
- uint64_t destAddr = sourceAddr;
+ uint64_t destAddr;
// If there is a branch relocation at the same offset we must use this to
// find the destination address as the branch could be indirected via a thunk
// or the PLT.
}
ScanResult scanRes = {0, 0, nullptr};
- const uint8_t *buf = isec->data().begin();
+ const uint8_t *buf = isec->content().begin();
// ARMv7-A Thumb 32-bit instructions are encoded 2 consecutive
// little-endian halfwords.
const ulittle16_t *instBuf = reinterpret_cast<const ulittle16_t *>(buf + off);
// Find a relocation for the branch if it exists. This will be used
// to determine the target.
uint64_t branchOff = off + 4;
- auto relIt = llvm::find_if(isec->relocations, [=](const Relocation &r) {
+ auto relIt = llvm::find_if(isec->relocs(), [=](const Relocation &r) {
return r.offset == branchOff &&
(r.type == R_ARM_THM_JUMP19 || r.type == R_ARM_THM_JUMP24 ||
r.type == R_ARM_THM_CALL);
});
- if (relIt != isec->relocations.end())
+ if (relIt != isec->relocs().end())
scanRes.rel = &(*relIt);
if (branchDestInFirstRegion(isec, branchOff, instr2, scanRes.rel)) {
if (patchInRange(isec, branchOff, instr2)) {
};
// Collect mapping symbols for every executable InputSection.
- for (InputFile *file : objectFiles) {
- auto *f = cast<ObjFile<ELF32LE>>(file);
- for (Symbol *s : f->getLocalSymbols()) {
+ for (ELFFileBase *file : ctx.objectFiles) {
+ for (Symbol *s : file->getLocalSymbols()) {
auto *def = dyn_cast<Defined>(s);
if (!def)
continue;
// determine the insertion point. This is ok as we only merge into an
// InputSectionDescription once per pass, and at the end of the pass
// assignAddresses() will recalculate all the outSecOff values.
- std::vector<InputSection *> tmp;
+ SmallVector<InputSection *, 0> tmp;
tmp.reserve(isd.sections.size() + patches.size());
auto mergeCmp = [](const InputSection *a, const InputSection *b) {
if (a->outSecOff != b->outSecOff)
patchRelType = R_ARM_JUMP24;
patchRelAddend -= 4;
}
- psec->relocations.push_back(
+ psec->addReloc(
Relocation{sr.rel->expr, patchRelType, 0, patchRelAddend, sr.rel->sym});
// Redirect the existing branch relocation to the patch.
sr.rel->expr = R_PC;
type = R_ARM_THM_JUMP24;
else
type = R_ARM_THM_CALL;
- isec->relocations.push_back(
- Relocation{R_PC, type, sr.off, -4, psec->patchSym});
+ isec->addReloc(Relocation{R_PC, type, sr.off, -4, psec->patchSym});
}
patches.push_back(psec);
}
while (thumbSym != mapSyms.end()) {
auto nonThumbSym = std::next(thumbSym);
uint64_t off = (*thumbSym)->value;
- uint64_t limit = (nonThumbSym == mapSyms.end()) ? isec->data().size()
- : (*nonThumbSym)->value;
+ uint64_t limit = nonThumbSym == mapSyms.end() ? isec->content().size()
+ : (*nonThumbSym)->value;
while (off < limit) {
ScanResult sr = scanCortexA8Errata657417(isec, off, limit);
for (OutputSection *os : outputSections) {
if (!(os->flags & SHF_ALLOC) || !(os->flags & SHF_EXECINSTR))
continue;
- for (BaseCommand *bc : os->sectionCommands)
- if (auto *isd = dyn_cast<InputSectionDescription>(bc)) {
+ for (SectionCommand *cmd : os->commands)
+ if (auto *isd = dyn_cast<InputSectionDescription>(cmd)) {
std::vector<Patch657417Section *> patches =
patchInputSectionDescription(*isd);
if (!patches.empty()) {
#include "lld/Common/LLVM.h"
#include "llvm/ADT/DenseMap.h"
-#include <map>
#include <vector>
-namespace lld {
-namespace elf {
+namespace lld::elf {
class Defined;
class InputSection;
class InputSectionDescription;
-class OutputSection;
class Patch657417Section;
class ARMErr657417Patcher {
bool initialized = false;
};
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
#include "Symbols.h"
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
-#include "llvm/Object/ELF.h"
+#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
AMDGPU::AMDGPU() {
relativeRel = R_AMDGPU_RELATIVE64;
gotRel = R_AMDGPU_ABS64;
- noneRel = R_AMDGPU_NONE;
symbolicRel = R_AMDGPU_ABS64;
}
}
uint32_t AMDGPU::calcEFlagsV3() const {
- uint32_t ret = getEFlags(objectFiles[0]);
+ uint32_t ret = getEFlags(ctx.objectFiles[0]);
// Verify that all input files have the same e_flags.
- for (InputFile *f : makeArrayRef(objectFiles).slice(1)) {
+ for (InputFile *f : ArrayRef(ctx.objectFiles).slice(1)) {
if (ret == getEFlags(f))
continue;
error("incompatible e_flags: " + toString(f));
}
uint32_t AMDGPU::calcEFlagsV4() const {
- uint32_t retMach = getEFlags(objectFiles[0]) & EF_AMDGPU_MACH;
- uint32_t retXnack = getEFlags(objectFiles[0]) & EF_AMDGPU_FEATURE_XNACK_V4;
+ uint32_t retMach = getEFlags(ctx.objectFiles[0]) & EF_AMDGPU_MACH;
+ uint32_t retXnack =
+ getEFlags(ctx.objectFiles[0]) & EF_AMDGPU_FEATURE_XNACK_V4;
uint32_t retSramEcc =
- getEFlags(objectFiles[0]) & EF_AMDGPU_FEATURE_SRAMECC_V4;
+ getEFlags(ctx.objectFiles[0]) & EF_AMDGPU_FEATURE_SRAMECC_V4;
// Verify that all input files have compatible e_flags (same mach, all
// features in the same category are either ANY, ANY and ON, or ANY and OFF).
- for (InputFile *f : makeArrayRef(objectFiles).slice(1)) {
+ for (InputFile *f : ArrayRef(ctx.objectFiles).slice(1)) {
if (retMach != (getEFlags(f) & EF_AMDGPU_MACH)) {
error("incompatible mach: " + toString(f));
return 0;
}
uint32_t AMDGPU::calcEFlags() const {
- assert(!objectFiles.empty());
+ if (ctx.objectFiles.empty())
+ return 0;
- uint8_t abiVersion = cast<ObjFile<ELF64LE>>(objectFiles[0])->getObj()
- .getHeader().e_ident[EI_ABIVERSION];
+ uint8_t abiVersion = cast<ObjFile<ELF64LE>>(ctx.objectFiles[0])
+ ->getObj()
+ .getHeader()
+ .e_ident[EI_ABIVERSION];
switch (abiVersion) {
case ELFABIVERSION_AMDGPU_HSA_V2:
case ELFABIVERSION_AMDGPU_HSA_V3:
return calcEFlagsV3();
case ELFABIVERSION_AMDGPU_HSA_V4:
+ case ELFABIVERSION_AMDGPU_HSA_V5:
return calcEFlagsV4();
default:
error("unknown abi version: " + Twine(abiVersion));
//
//===----------------------------------------------------------------------===//
-#include "InputFiles.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
-#include "Thunks.h"
#include "lld/Common/ErrorHandler.h"
-#include "llvm/Object/ELF.h"
+#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
relativeRel = R_ARM_RELATIVE;
iRelativeRel = R_ARM_IRELATIVE;
gotRel = R_ARM_GLOB_DAT;
- noneRel = R_ARM_NONE;
pltRel = R_ARM_JUMP_SLOT;
symbolicRel = R_ARM_ABS32;
tlsGotRel = R_ARM_TLS_TPOFF32;
tlsModuleIndexRel = R_ARM_TLS_DTPMOD32;
tlsOffsetRel = R_ARM_TLS_DTPOFF32;
- gotBaseSymInGotPlt = false;
pltHeaderSize = 32;
pltEntrySize = 16;
ipltEntrySize = 16;
RelExpr ARM::getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const {
switch (type) {
+ case R_ARM_ABS32:
+ case R_ARM_MOVW_ABS_NC:
+ case R_ARM_MOVT_ABS:
+ case R_ARM_THM_MOVW_ABS_NC:
+ case R_ARM_THM_MOVT_ABS:
+ return R_ABS;
+ case R_ARM_THM_JUMP8:
case R_ARM_THM_JUMP11:
return R_PC;
case R_ARM_CALL:
case R_ARM_THM_MOVT_PREL:
return R_PC;
case R_ARM_ALU_PC_G0:
+ case R_ARM_ALU_PC_G0_NC:
+ case R_ARM_ALU_PC_G1:
+ case R_ARM_ALU_PC_G1_NC:
+ case R_ARM_ALU_PC_G2:
case R_ARM_LDR_PC_G0:
+ case R_ARM_LDR_PC_G1:
+ case R_ARM_LDR_PC_G2:
+ case R_ARM_LDRS_PC_G0:
+ case R_ARM_LDRS_PC_G1:
+ case R_ARM_LDRS_PC_G2:
case R_ARM_THM_ALU_PREL_11_0:
case R_ARM_THM_PC8:
case R_ARM_THM_PC12:
// not ARMv4 output, we can just ignore it.
return R_NONE;
default:
- return R_ABS;
+ error(getErrorLocation(loc) + "unknown relocation (" + Twine(type) +
+ ") against symbol " + toString(s));
+ return R_NONE;
}
}
}
// Long form PLT Header that does not have any restrictions on the displacement
-// of the .plt from the .plt.got.
+// of the .plt from the .got.plt.
static void writePltHeaderLong(uint8_t *buf) {
const uint8_t pltData[] = {
0x04, 0xe0, 0x2d, 0xe5, // str lr, [sp,#-4]!
write32le(buf + 16, gotPlt - l1 - 8);
}
-// The default PLT header requires the .plt.got to be within 128 Mb of the
+// The default PLT header requires the .got.plt to be within 128 Mb of the
// .plt in the positive direction.
void ARM::writePltHeader(uint8_t *buf) const {
// Use a similar sequence to that in writePlt(), the difference is the calling
}
// Long form PLT entries that do not have any restrictions on the displacement
-// of the .plt from the .plt.got.
+// of the .plt from the .got.plt.
static void writePltLong(uint8_t *buf, uint64_t gotPltEntryAddr,
uint64_t pltEntryAddr) {
const uint8_t pltData[] = {
0x04, 0xc0, 0x9f, 0xe5, // ldr ip, L2
0x0f, 0xc0, 0x8c, 0xe0, // L1: add ip, ip, pc
0x00, 0xf0, 0x9c, 0xe5, // ldr pc, [ip]
- 0x00, 0x00, 0x00, 0x00, // L2: .word Offset(&(.plt.got) - L1 - 8
+ 0x00, 0x00, 0x00, 0x00, // L2: .word Offset(&(.got.plt) - L1 - 8
};
memcpy(buf, pltData, sizeof(pltData));
uint64_t l1 = pltEntryAddr + 4;
write32le(buf + 12, gotPltEntryAddr - l1 - 8);
}
-// The default PLT entries require the .plt.got to be within 128 Mb of the
+// The default PLT entries require the .got.plt to be within 128 Mb of the
// .plt in the positive direction.
void ARM::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const {
// hard code the most compact rotations for simplicity. This saves a load
// instruction over the long plt sequences.
const uint32_t pltData[] = {
- 0xe28fc600, // L1: add ip, pc, #0x0NN00000 Offset(&(.plt.got) - L1 - 8
- 0xe28cca00, // add ip, ip, #0x000NN000 Offset(&(.plt.got) - L1 - 8
- 0xe5bcf000, // ldr pc, [ip, #0x00000NNN] Offset(&(.plt.got) - L1 - 8
+ 0xe28fc600, // L1: add ip, pc, #0x0NN00000 Offset(&(.got.plt) - L1 - 8
+ 0xe28cca00, // add ip, ip, #0x000NN000 Offset(&(.got.plt) - L1 - 8
+ 0xe5bcf000, // ldr pc, [ip, #0x00000NNN] Offset(&(.got.plt) - L1 - 8
};
uint64_t offset = sym.getGotPltVA() - pltEntryAddr - 8;
bool ARM::needsThunk(RelExpr expr, RelType type, const InputFile *file,
uint64_t branchAddr, const Symbol &s,
int64_t a) const {
- // If S is an undefined weak symbol and does not have a PLT entry then it
- // will be resolved as a branch to the next instruction.
- if (s.isUndefWeak() && !s.isInPlt())
+ // If s is an undefined weak symbol and does not have a PLT entry then it will
+ // be resolved as a branch to the next instruction. If it is hidden, its
+ // binding has been converted to local, so we just check isUndefined() here. A
+ // undefined non-weak symbol will have been errored.
+ if (s.isUndefined() && !s.isInPlt())
return false;
// A state change from ARM to Thumb and vice versa must go through an
// interworking thunk if the relocation type is not R_ARM_CALL or
// Otherwise we need to interwork if STT_FUNC Symbol has bit 0 set (Thumb).
if (s.isFunc() && expr == R_PC && (s.getVA() & 1))
return true;
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
case R_ARM_CALL: {
uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA() : s.getVA();
- return !inBranchRange(type, branchAddr, dst + a);
+ return !inBranchRange(type, branchAddr, dst + a) ||
+ (!config->armHasBlx && (s.getVA() & 1));
}
case R_ARM_THM_JUMP19:
case R_ARM_THM_JUMP24:
// Otherwise we need to interwork if STT_FUNC Symbol has bit 0 clear (ARM).
if (expr == R_PLT_PC || (s.isFunc() && (s.getVA() & 1) == 0))
return true;
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
case R_ARM_THM_CALL: {
uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA() : s.getVA();
- return !inBranchRange(type, branchAddr, dst + a);
+ return !inBranchRange(type, branchAddr, dst + a) ||
+ (!config->armHasBlx && (s.getVA() & 1) == 0);;
}
}
return false;
// or Thumb.
static void stateChangeWarning(uint8_t *loc, RelType relt, const Symbol &s) {
assert(!s.isFunc());
+ const ErrorPlace place = getErrorPlace(loc);
+ std::string hint;
+ if (!place.srcLoc.empty())
+ hint = "; " + place.srcLoc;
if (s.isSection()) {
// Section symbols must be defined and in a section. Users cannot change
// the type. Use the section name as getName() returns an empty string.
- warn(getErrorLocation(loc) + "branch and link relocation: " +
- toString(relt) + " to STT_SECTION symbol " +
- cast<Defined>(s).section->name + " ; interworking not performed");
+ warn(place.loc + "branch and link relocation: " + toString(relt) +
+ " to STT_SECTION symbol " + cast<Defined>(s).section->name +
+ " ; interworking not performed" + hint);
} else {
// Warn with hint on how to alter the symbol type.
warn(getErrorLocation(loc) + "branch and link relocation: " +
toString(relt) + " to non STT_FUNC symbol: " + s.getName() +
" interworking not performed; consider using directive '.type " +
s.getName() +
- ", %function' to give symbol type STT_FUNC if"
- " interworking between ARM and Thumb is required");
+ ", %function' to give symbol type STT_FUNC if interworking between "
+ "ARM and Thumb is required" +
+ hint);
}
}
-// Utility functions taken from ARMAddressingModes.h, only changes are LLD
-// coding style.
-
// Rotate a 32-bit unsigned value right by a specified amt of bits.
static uint32_t rotr32(uint32_t val, uint32_t amt) {
assert(amt < 32 && "Invalid rotate amount");
return (val >> amt) | (val << ((32 - amt) & 31));
}
-// Rotate a 32-bit unsigned value left by a specified amt of bits.
-static uint32_t rotl32(uint32_t val, uint32_t amt) {
- assert(amt < 32 && "Invalid rotate amount");
- return (val << amt) | (val >> ((32 - amt) & 31));
+static std::pair<uint32_t, uint32_t> getRemAndLZForGroup(unsigned group,
+ uint32_t val) {
+ uint32_t rem, lz;
+ do {
+ lz = llvm::countLeadingZeros(val) & ~1;
+ rem = val;
+ if (lz == 32) // implies rem == 0
+ break;
+ val &= 0xffffff >> lz;
+ } while (group--);
+ return {rem, lz};
}
-// Try to encode a 32-bit unsigned immediate imm with an immediate shifter
-// operand, this form is an 8-bit immediate rotated right by an even number of
-// bits. We compute the rotate amount to use. If this immediate value cannot be
-// handled with a single shifter-op, determine a good rotate amount that will
-// take a maximal chunk of bits out of the immediate.
-static uint32_t getSOImmValRotate(uint32_t imm) {
- // 8-bit (or less) immediates are trivially shifter_operands with a rotate
- // of zero.
- if ((imm & ~255U) == 0)
- return 0;
-
- // Use CTZ to compute the rotate amount.
- unsigned tz = llvm::countTrailingZeros(imm);
-
- // Rotate amount must be even. Something like 0x200 must be rotated 8 bits,
- // not 9.
- unsigned rotAmt = tz & ~1;
-
- // If we can handle this spread, return it.
- if ((rotr32(imm, rotAmt) & ~255U) == 0)
- return (32 - rotAmt) & 31; // HW rotates right, not left.
+static void encodeAluGroup(uint8_t *loc, const Relocation &rel, uint64_t val,
+ int group, bool check) {
+ // ADD/SUB (immediate) add = bit23, sub = bit22
+ // immediate field carries is a 12-bit modified immediate, made up of a 4-bit
+ // even rotate right and an 8-bit immediate.
+ uint32_t opcode = 0x00800000;
+ if (val >> 63) {
+ opcode = 0x00400000;
+ val = -val;
+ }
+ uint32_t imm, lz;
+ std::tie(imm, lz) = getRemAndLZForGroup(group, val);
+ uint32_t rot = 0;
+ if (lz < 24) {
+ imm = rotr32(imm, 24 - lz);
+ rot = (lz + 8) << 7;
+ }
+ if (check && imm > 0xff)
+ error(getErrorLocation(loc) + "unencodeable immediate " + Twine(val).str() +
+ " for relocation " + toString(rel.type));
+ write32le(loc, (read32le(loc) & 0xff3ff000) | opcode | rot | (imm & 0xff));
+}
- // For values like 0xF000000F, we should ignore the low 6 bits, then
- // retry the hunt.
- if (imm & 63U) {
- unsigned tz2 = countTrailingZeros(imm & ~63U);
- unsigned rotAmt2 = tz2 & ~1;
- if ((rotr32(imm, rotAmt2) & ~255U) == 0)
- return (32 - rotAmt2) & 31; // HW rotates right, not left.
+static void encodeLdrGroup(uint8_t *loc, const Relocation &rel, uint64_t val,
+ int group) {
+ // R_ARM_LDR_PC_Gn is S + A - P, we have ((S + A) | T) - P, if S is a
+ // function then addr is 0 (modulo 2) and Pa is 0 (modulo 4) so we can clear
+ // bottom bit to recover S + A - P.
+ if (rel.sym->isFunc())
+ val &= ~0x1;
+ // LDR (literal) u = bit23
+ uint32_t opcode = 0x00800000;
+ if (val >> 63) {
+ opcode = 0x0;
+ val = -val;
}
+ uint32_t imm = getRemAndLZForGroup(group, val).first;
+ checkUInt(loc, imm, 12, rel);
+ write32le(loc, (read32le(loc) & 0xff7ff000) | opcode | imm);
+}
- // Otherwise, we have no way to cover this span of bits with a single
- // shifter_op immediate. Return a chunk of bits that will be useful to
- // handle.
- return (32 - rotAmt) & 31; // HW rotates right, not left.
+static void encodeLdrsGroup(uint8_t *loc, const Relocation &rel, uint64_t val,
+ int group) {
+ // R_ARM_LDRS_PC_Gn is S + A - P, we have ((S + A) | T) - P, if S is a
+ // function then addr is 0 (modulo 2) and Pa is 0 (modulo 4) so we can clear
+ // bottom bit to recover S + A - P.
+ if (rel.sym->isFunc())
+ val &= ~0x1;
+ // LDRD/LDRH/LDRSB/LDRSH (literal) u = bit23
+ uint32_t opcode = 0x00800000;
+ if (val >> 63) {
+ opcode = 0x0;
+ val = -val;
+ }
+ uint32_t imm = getRemAndLZForGroup(group, val).first;
+ checkUInt(loc, imm, 8, rel);
+ write32le(loc, (read32le(loc) & 0xff7ff0f0) | opcode | ((imm & 0xf0) << 4) |
+ (imm & 0xf));
}
void ARM::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
write32le(loc, 0xeb000000 | (read32le(loc) & 0x00ffffff));
// fall through as BL encoding is shared with B
}
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
case R_ARM_JUMP24:
case R_ARM_PC24:
case R_ARM_PLT32:
checkInt(loc, val, 26, rel);
write32le(loc, (read32le(loc) & ~0x00ffffff) | ((val >> 2) & 0x00ffffff));
break;
+ case R_ARM_THM_JUMP8:
+ // We do a 9 bit check because val is right-shifted by 1 bit.
+ checkInt(loc, val, 9, rel);
+ write16le(loc, (read32le(loc) & 0xff00) | ((val >> 1) & 0x00ff));
+ break;
case R_ARM_THM_JUMP11:
+ // We do a 12 bit check because val is right-shifted by 1 bit.
checkInt(loc, val, 12, rel);
write16le(loc, (read32le(loc) & 0xf800) | ((val >> 1) & 0x07ff));
break;
}
}
// Fall through as rest of encoding is the same as B.W
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
case R_ARM_THM_JUMP24:
// Encoding B T4, BL T1, BLX T2: Val = S:I1:I2:imm10:imm11:0
checkInt(loc, val, 25, rel);
((val << 4) & 0x7000) | // imm3
(val & 0x00ff)); // imm8
break;
- case R_ARM_ALU_PC_G0: {
- // ADR (literal) add = bit23, sub = bit22
- // literal is a 12-bit modified immediate, made up of a 4-bit even rotate
- // right and an 8-bit immediate. The code-sequence here is derived from
- // ARMAddressingModes.h in llvm/Target/ARM/MCTargetDesc. In our case we
- // want to give an error if we cannot encode the constant.
- uint32_t opcode = 0x00800000;
- if (val >> 63) {
- opcode = 0x00400000;
- val = ~val + 1;
- }
- if ((val & ~255U) != 0) {
- uint32_t rotAmt = getSOImmValRotate(val);
- // Error if we cannot encode this with a single shift
- if (rotr32(~255U, rotAmt) & val)
- error(getErrorLocation(loc) + "unencodeable immediate " +
- Twine(val).str() + " for relocation " + toString(rel.type));
- val = rotl32(val, rotAmt) | ((rotAmt >> 1) << 8);
- }
- write32le(loc, (read32le(loc) & 0xff0ff000) | opcode | val);
+ case R_ARM_ALU_PC_G0:
+ encodeAluGroup(loc, rel, val, 0, true);
break;
- }
- case R_ARM_LDR_PC_G0: {
- // R_ARM_LDR_PC_G0 is S + A - P, we have ((S + A) | T) - P, if S is a
- // function then addr is 0 (modulo 2) and Pa is 0 (modulo 4) so we can clear
- // bottom bit to recover S + A - P.
- if (rel.sym->isFunc())
- val &= ~0x1;
- // LDR (literal) u = bit23
- int64_t imm = val;
- uint32_t u = 0x00800000;
- if (imm < 0) {
- imm = -imm;
- u = 0;
- }
- checkUInt(loc, imm, 12, rel);
- write32le(loc, (read32le(loc) & 0xff7ff000) | u | imm);
+ case R_ARM_ALU_PC_G0_NC:
+ encodeAluGroup(loc, rel, val, 0, false);
+ break;
+ case R_ARM_ALU_PC_G1:
+ encodeAluGroup(loc, rel, val, 1, true);
+ break;
+ case R_ARM_ALU_PC_G1_NC:
+ encodeAluGroup(loc, rel, val, 1, false);
+ break;
+ case R_ARM_ALU_PC_G2:
+ encodeAluGroup(loc, rel, val, 2, true);
+ break;
+ case R_ARM_LDR_PC_G0:
+ encodeLdrGroup(loc, rel, val, 0);
+ break;
+ case R_ARM_LDR_PC_G1:
+ encodeLdrGroup(loc, rel, val, 1);
+ break;
+ case R_ARM_LDR_PC_G2:
+ encodeLdrGroup(loc, rel, val, 2);
+ break;
+ case R_ARM_LDRS_PC_G0:
+ encodeLdrsGroup(loc, rel, val, 0);
+ break;
+ case R_ARM_LDRS_PC_G1:
+ encodeLdrsGroup(loc, rel, val, 1);
+ break;
+ case R_ARM_LDRS_PC_G2:
+ encodeLdrsGroup(loc, rel, val, 2);
break;
- }
case R_ARM_THM_ALU_PREL_11_0: {
// ADR encoding T2 (sub), T3 (add) i:imm3:imm8
int64_t imm = val;
break;
}
default:
- error(getErrorLocation(loc) + "unrecognized relocation " +
- toString(rel.type));
+ llvm_unreachable("unknown relocation");
}
}
case R_ARM_PC24:
case R_ARM_PLT32:
return SignExtend64<26>(read32le(buf) << 2);
+ case R_ARM_THM_JUMP8:
+ return SignExtend64<9>(read16le(buf) << 1);
case R_ARM_THM_JUMP11:
return SignExtend64<12>(read16le(buf) << 1);
case R_ARM_THM_JUMP19: {
((lo & 0x7ff) << 1)); // imm11:0
break;
}
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
case R_ARM_THM_JUMP24: {
// Encoding B T4, BL T1, BLX T2: A = S:I1:I2:imm10:imm11:0
// I1 = NOT(J1 EOR S), I2 = NOT(J2 EOR S)
((lo & 0x7000) >> 4) | // imm3
(lo & 0x00ff)); // imm8
}
- case R_ARM_ALU_PC_G0: {
+ case R_ARM_ALU_PC_G0:
+ case R_ARM_ALU_PC_G0_NC:
+ case R_ARM_ALU_PC_G1:
+ case R_ARM_ALU_PC_G1_NC:
+ case R_ARM_ALU_PC_G2: {
// 12-bit immediate is a modified immediate made up of a 4-bit even
// right rotation and 8-bit constant. After the rotation the value
// is zero-extended. When bit 23 is set the instruction is an add, when
uint32_t val = rotr32(instr & 0xff, ((instr & 0xf00) >> 8) * 2);
return (instr & 0x00400000) ? -val : val;
}
- case R_ARM_LDR_PC_G0: {
+ case R_ARM_LDR_PC_G0:
+ case R_ARM_LDR_PC_G1:
+ case R_ARM_LDR_PC_G2: {
// ADR (literal) add = bit23, sub = bit22
// LDR (literal) u = bit23 unsigned imm12
bool u = read32le(buf) & 0x00800000;
uint32_t imm12 = read32le(buf) & 0xfff;
return u ? imm12 : -imm12;
}
+ case R_ARM_LDRS_PC_G0:
+ case R_ARM_LDRS_PC_G1:
+ case R_ARM_LDRS_PC_G2: {
+ // LDRD/LDRH/LDRSB/LDRSH (literal) u = bit23 unsigned imm8
+ uint32_t opcode = read32le(buf);
+ bool u = opcode & 0x00800000;
+ uint32_t imm4l = opcode & 0xf;
+ uint32_t imm4h = (opcode & 0xf00) >> 4;
+ return u ? (imm4h | imm4l) : -(imm4h | imm4l);
+ }
case R_ARM_THM_ALU_PREL_11_0: {
// Thumb2 ADR, which is an alias for a sub or add instruction with an
// unsigned immediate.
return u ? imm12 : -imm12;
}
case R_ARM_NONE:
+ case R_ARM_V4BX:
case R_ARM_JUMP_SLOT:
// These relocations are defined as not having an implicit addend.
return 0;
#include "Symbols.h"
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
-#include "llvm/Object/ELF.h"
+#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
namespace {
class AVR final : public TargetInfo {
public:
- AVR();
uint32_t calcEFlags() const override;
RelExpr getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const override;
};
} // namespace
-AVR::AVR() { noneRel = R_AVR_NONE; }
-
RelExpr AVR::getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const {
switch (type) {
case R_AVR_HI8_LDI_PM_NEG:
case R_AVR_HH8_LDI_PM:
case R_AVR_HH8_LDI_PM_NEG:
+ case R_AVR_LDS_STS_16:
case R_AVR_PORT5:
case R_AVR_PORT6:
case R_AVR_CALL:
writeLDI(loc, (-val >> 17) & 0xff);
break;
+ case R_AVR_LDS_STS_16: {
+ checkUInt(loc, val, 7, rel);
+ const uint16_t hi = val >> 4;
+ const uint16_t lo = val & 0xf;
+ write16le(loc, (read16le(loc) & 0xf8f0) | ((hi << 8) | lo));
+ break;
+ }
+
case R_AVR_PORT5:
checkUInt(loc, val, 5, rel);
write16le(loc, (read16le(loc) & 0xff07) | (val << 3));
}
uint32_t AVR::calcEFlags() const {
- assert(!objectFiles.empty());
+ assert(!ctx.objectFiles.empty());
- uint32_t flags = getEFlags(objectFiles[0]);
+ uint32_t flags = getEFlags(ctx.objectFiles[0]);
bool hasLinkRelaxFlag = flags & EF_AVR_LINKRELAX_PREPARED;
- for (InputFile *f : makeArrayRef(objectFiles).slice(1)) {
+ for (InputFile *f : ArrayRef(ctx.objectFiles).slice(1)) {
uint32_t objFlags = getEFlags(f);
if ((objFlags & EF_AVR_ARCH_MASK) != (flags & EF_AVR_ARCH_MASK))
error(toString(f) +
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/BinaryFormat/ELF.h"
-#include "llvm/Object/ELF.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
gotRel = R_HEX_GLOB_DAT;
symbolicRel = R_HEX_32;
+ gotBaseSymInGotPlt = true;
// The zero'th GOT entry is reserved for the address of _DYNAMIC. The
// next 3 are reserved for the dynamic loader.
gotPltHeaderEntriesNum = 4;
// Hexagon Linux uses 64K pages by default.
defaultMaxPageSize = 0x10000;
- noneRel = R_HEX_NONE;
tlsGotRel = R_HEX_TPREL_32;
tlsModuleIndexRel = R_HEX_DTPMOD_32;
tlsOffsetRel = R_HEX_DTPREL_32;
}
uint32_t Hexagon::calcEFlags() const {
- assert(!objectFiles.empty());
+ assert(!ctx.objectFiles.empty());
// The architecture revision must always be equal to or greater than
// greatest revision in the list of inputs.
uint32_t ret = 0;
- for (InputFile *f : objectFiles) {
+ for (InputFile *f : ctx.objectFiles) {
uint32_t eflags = cast<ObjFile<ELF32LE>>(f)->getObj().getHeader().e_flags;
if (eflags > ret)
ret = eflags;
case R_HEX_IE_GOT_32_6_X:
case R_HEX_IE_GOT_HI16:
case R_HEX_IE_GOT_LO16:
- config->hasStaticTlsModel = true;
return R_GOTPLT;
case R_HEX_TPREL_11_X:
case R_HEX_TPREL_16:
}
}
+// There are (arguably too) many relocation masks for the DSP's
+// R_HEX_6_X type. The table below is used to select the correct mask
+// for the given instruction.
+struct InstructionMask {
+ uint32_t cmpMask;
+ uint32_t relocMask;
+};
+static const InstructionMask r6[] = {
+ {0x38000000, 0x0000201f}, {0x39000000, 0x0000201f},
+ {0x3e000000, 0x00001f80}, {0x3f000000, 0x00001f80},
+ {0x40000000, 0x000020f8}, {0x41000000, 0x000007e0},
+ {0x42000000, 0x000020f8}, {0x43000000, 0x000007e0},
+ {0x44000000, 0x000020f8}, {0x45000000, 0x000007e0},
+ {0x46000000, 0x000020f8}, {0x47000000, 0x000007e0},
+ {0x6a000000, 0x00001f80}, {0x7c000000, 0x001f2000},
+ {0x9a000000, 0x00000f60}, {0x9b000000, 0x00000f60},
+ {0x9c000000, 0x00000f60}, {0x9d000000, 0x00000f60},
+ {0x9f000000, 0x001f0100}, {0xab000000, 0x0000003f},
+ {0xad000000, 0x0000003f}, {0xaf000000, 0x00030078},
+ {0xd7000000, 0x006020e0}, {0xd8000000, 0x006020e0},
+ {0xdb000000, 0x006020e0}, {0xdf000000, 0x006020e0}};
+
static bool isDuplex(uint32_t insn) {
// Duplex forms have a fixed mask and parse bits 15:14 are always
// zero. Non-duplex insns will always have at least one bit set in the
}
static uint32_t findMaskR6(uint32_t insn) {
- // There are (arguably too) many relocation masks for the DSP's
- // R_HEX_6_X type. The table below is used to select the correct mask
- // for the given instruction.
- struct InstructionMask {
- uint32_t cmpMask;
- uint32_t relocMask;
- };
-
- static const InstructionMask r6[] = {
- {0x38000000, 0x0000201f}, {0x39000000, 0x0000201f},
- {0x3e000000, 0x00001f80}, {0x3f000000, 0x00001f80},
- {0x40000000, 0x000020f8}, {0x41000000, 0x000007e0},
- {0x42000000, 0x000020f8}, {0x43000000, 0x000007e0},
- {0x44000000, 0x000020f8}, {0x45000000, 0x000007e0},
- {0x46000000, 0x000020f8}, {0x47000000, 0x000007e0},
- {0x6a000000, 0x00001f80}, {0x7c000000, 0x001f2000},
- {0x9a000000, 0x00000f60}, {0x9b000000, 0x00000f60},
- {0x9c000000, 0x00000f60}, {0x9d000000, 0x00000f60},
- {0x9f000000, 0x001f0100}, {0xab000000, 0x0000003f},
- {0xad000000, 0x0000003f}, {0xaf000000, 0x00030078},
- {0xd7000000, 0x006020e0}, {0xd8000000, 0x006020e0},
- {0xdb000000, 0x006020e0}, {0xdf000000, 0x006020e0}};
-
if (isDuplex(insn))
return 0x03f00000;
if ((0xff000000 & insn) == i.cmpMask)
return i.relocMask;
- error("unrecognized instruction for R_HEX_6 relocation: 0x" +
+ error("unrecognized instruction for 6_X relocation: 0x" +
utohexstr(insn));
return 0;
}
if (isDuplex(insn))
return 0x03f00000;
- error("unrecognized instruction for R_HEX_16_X relocation: 0x" +
+ for (InstructionMask i : r6)
+ if ((0xff000000 & insn) == i.cmpMask)
+ return i.relocMask;
+
+ error("unrecognized instruction for 16_X type: 0x" +
utohexstr(insn));
return 0;
}
//
//===----------------------------------------------------------------------===//
-#include "InputFiles.h"
#include "Symbols.h"
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
-#include "llvm/Object/ELF.h"
+#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Endian.h"
using namespace llvm;
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
-#include "Thunks.h"
#include "lld/Common/ErrorHandler.h"
-#include "llvm/Object/ELF.h"
+#include "llvm/BinaryFormat/ELF.h"
using namespace llvm;
using namespace llvm::object;
template <class ELFT> MIPS<ELFT>::MIPS() {
gotPltHeaderEntriesNum = 2;
defaultMaxPageSize = 65536;
- gotBaseSymInGotPlt = false;
pltEntrySize = 16;
pltHeaderSize = 32;
copyRel = R_MIPS_COPY;
- noneRel = R_MIPS_NONE;
pltRel = R_MIPS_JUMP_SLOT;
needsThunks = true;
return R_MIPS_GOT_GP_PC;
if (&s == ElfSym::mipsLocalGp)
return R_MIPS_GOT_GP;
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
case R_MIPS_32:
case R_MIPS_64:
case R_MIPS_GOT_OFST:
case R_MIPS_SUB:
+ return R_ABS;
case R_MIPS_TLS_DTPREL_HI16:
case R_MIPS_TLS_DTPREL_LO16:
case R_MIPS_TLS_DTPREL32:
case R_MIPS_TLS_DTPREL64:
case R_MICROMIPS_TLS_DTPREL_HI16:
case R_MICROMIPS_TLS_DTPREL_LO16:
- return R_ABS;
+ return R_DTPREL;
case R_MIPS_TLS_TPREL_HI16:
case R_MIPS_TLS_TPREL_LO16:
case R_MIPS_TLS_TPREL32:
case R_MICROMIPS_GOT16:
if (s.isLocal())
return R_MIPS_GOT_LOCAL_PAGE;
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
case R_MIPS_CALL16:
case R_MIPS_GOT_DISP:
case R_MIPS_TLS_GOTTPREL:
case R_MIPS_TLS_GOTTPREL:
case R_MIPS_TLS_LDM:
checkInt(loc, val, 16, rel);
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
case R_MIPS_CALL_LO16:
case R_MIPS_GOT_LO16:
case R_MIPS_GOT_OFST:
#include "lld/Common/ErrorHandler.h"
#include "llvm/BinaryFormat/ELF.h"
-#include "llvm/Object/ELF.h"
#include "llvm/Support/MipsABIFlags.h"
using namespace llvm;
template <class ELFT> uint32_t elf::calcMipsEFlags() {
std::vector<FileFlags> v;
- for (InputFile *f : objectFiles)
+ for (InputFile *f : ctx.objectFiles)
v.push_back({f, cast<ObjFile<ELFT>>(f)->getObj().getHeader().e_flags});
if (v.empty()) {
// If we don't have any input files, we'll have to rely on the information
//
//===----------------------------------------------------------------------===//
-#include "InputFiles.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
SPARCV9::SPARCV9() {
copyRel = R_SPARC_COPY;
gotRel = R_SPARC_GLOB_DAT;
- noneRel = R_SPARC_NONE;
pltRel = R_SPARC_JMP_SLOT;
relativeRel = R_SPARC_RELATIVE;
symbolicRel = R_SPARC_64;
//
//===----------------------------------------------------------------------===//
-#include "InputFiles.h"
+#include "OutputSections.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
uint64_t val) const override;
RelExpr adjustTlsExpr(RelType type, RelExpr expr) const override;
- void relaxTlsGdToIe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const override;
- void relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const override;
- void relaxTlsIeToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const override;
- void relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const override;
+ void relocateAlloc(InputSectionBase &sec, uint8_t *buf) const override;
};
} // namespace
X86::X86() {
copyRel = R_386_COPY;
gotRel = R_386_GLOB_DAT;
- noneRel = R_386_NONE;
pltRel = R_386_JUMP_SLOT;
iRelativeRel = R_386_IRELATIVE;
relativeRel = R_386_RELATIVE;
symbolicRel = R_386_32;
+ tlsDescRel = R_386_TLS_DESC;
tlsGotRel = R_386_TLS_TPOFF;
tlsModuleIndexRel = R_386_TLS_DTPMOD32;
tlsOffsetRel = R_386_TLS_DTPOFF32;
+ gotBaseSymInGotPlt = true;
pltHeaderSize = 16;
pltEntrySize = 16;
ipltEntrySize = 16;
}
int X86::getTlsGdRelaxSkip(RelType type) const {
- return 2;
+ // TLSDESC relocations are processed separately. See relaxTlsGdToLe below.
+ return type == R_386_TLS_GOTDESC || type == R_386_TLS_DESC_CALL ? 1 : 2;
}
RelExpr X86::getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const {
- // There are 4 different TLS variable models with varying degrees of
- // flexibility and performance. LocalExec and InitialExec models are fast but
- // less-flexible models. If they are in use, we set DF_STATIC_TLS flag in the
- // dynamic section to let runtime know about that.
- if (type == R_386_TLS_LE || type == R_386_TLS_LE_32 || type == R_386_TLS_IE ||
- type == R_386_TLS_GOTIE)
- config->hasStaticTlsModel = true;
-
switch (type) {
case R_386_8:
case R_386_16:
// the byte, we can determine whether the instruction uses the operand as an
// absolute address (R_GOT) or a register-relative address (R_GOTPLT).
return (loc[-1] & 0xc7) == 0x5 ? R_GOT : R_GOTPLT;
+ case R_386_TLS_GOTDESC:
+ return R_TLSDESC_GOTPLT;
+ case R_386_TLS_DESC_CALL:
+ return R_TLSDESC_CALL;
case R_386_TLS_GOTIE:
return R_GOTPLT;
case R_386_GOTOFF:
case R_RELAX_TLS_GD_TO_IE:
return R_RELAX_TLS_GD_TO_IE_GOTPLT;
case R_RELAX_TLS_GD_TO_LE:
- return R_RELAX_TLS_GD_TO_LE_NEG;
+ return type == R_386_TLS_GD ? R_RELAX_TLS_GD_TO_LE_NEG
+ : R_RELAX_TLS_GD_TO_LE;
}
}
void X86::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const {
- unsigned relOff = in.relaPlt->entsize * sym.pltIndex;
+ unsigned relOff = in.relaPlt->entsize * sym.getPltIdx();
if (config->isPic) {
const uint8_t inst[] = {
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
case R_386_PC32:
case R_386_PLT32:
case R_386_RELATIVE:
+ case R_386_TLS_GOTDESC:
+ case R_386_TLS_DESC_CALL:
case R_386_TLS_DTPMOD32:
case R_386_TLS_DTPOFF32:
case R_386_TLS_LDO_32:
case R_386_TLS_TPOFF:
case R_386_TLS_TPOFF32:
return SignExtend64<32>(read32le(buf));
+ case R_386_TLS_DESC:
+ return SignExtend64<32>(read32le(buf + 4));
case R_386_NONE:
case R_386_JUMP_SLOT:
// These relocations are defined as not having an implicit addend.
case R_386_PC32:
case R_386_PLT32:
case R_386_RELATIVE:
+ case R_386_TLS_GOTDESC:
+ case R_386_TLS_DESC_CALL:
case R_386_TLS_DTPMOD32:
case R_386_TLS_DTPOFF32:
case R_386_TLS_GD:
checkInt(loc, val, 32, rel);
write32le(loc, val);
break;
+ case R_386_TLS_DESC:
+ // The addend is stored in the second 32-bit word.
+ write32le(loc + 4, val);
+ break;
default:
llvm_unreachable("unknown relocation");
}
}
-void X86::relaxTlsGdToLe(uint8_t *loc, const Relocation &, uint64_t val) const {
- // Convert
- // leal x@tlsgd(, %ebx, 1),
- // call __tls_get_addr@plt
- // to
- // movl %gs:0,%eax
- // subl $x@ntpoff,%eax
- const uint8_t inst[] = {
- 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0, %eax
- 0x81, 0xe8, 0, 0, 0, 0, // subl Val(%ebx), %eax
- };
- memcpy(loc - 3, inst, sizeof(inst));
- write32le(loc + 5, val);
+static void relaxTlsGdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) {
+ if (rel.type == R_386_TLS_GD) {
+ // Convert (loc[-2] == 0x04)
+ // leal x@tlsgd(, %ebx, 1), %eax
+ // call ___tls_get_addr@plt
+ // or
+ // leal x@tlsgd(%reg), %eax
+ // call *___tls_get_addr@got(%reg)
+ // to
+ const uint8_t inst[] = {
+ 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0, %eax
+ 0x81, 0xe8, 0, 0, 0, 0, // subl x@ntpoff(%ebx), %eax
+ };
+ uint8_t *w = loc[-2] == 0x04 ? loc - 3 : loc - 2;
+ memcpy(w, inst, sizeof(inst));
+ write32le(w + 8, val);
+ } else if (rel.type == R_386_TLS_GOTDESC) {
+ // Convert leal x@tlsdesc(%ebx), %eax to leal x@ntpoff, %eax.
+ //
+ // Note: call *x@tlsdesc(%eax) may not immediately follow this instruction.
+ if (memcmp(loc - 2, "\x8d\x83", 2)) {
+ error(getErrorLocation(loc - 2) +
+ "R_386_TLS_GOTDESC must be used in leal x@tlsdesc(%ebx), %eax");
+ return;
+ }
+ loc[-1] = 0x05;
+ write32le(loc, val);
+ } else {
+ // Convert call *x@tlsdesc(%eax) to xchg ax, ax.
+ assert(rel.type == R_386_TLS_DESC_CALL);
+ loc[0] = 0x66;
+ loc[1] = 0x90;
+ }
}
-void X86::relaxTlsGdToIe(uint8_t *loc, const Relocation &, uint64_t val) const {
- // Convert
- // leal x@tlsgd(, %ebx, 1),
- // call __tls_get_addr@plt
- // to
- // movl %gs:0, %eax
- // addl x@gotntpoff(%ebx), %eax
- const uint8_t inst[] = {
- 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0, %eax
- 0x03, 0x83, 0, 0, 0, 0, // addl Val(%ebx), %eax
- };
- memcpy(loc - 3, inst, sizeof(inst));
- write32le(loc + 5, val);
+static void relaxTlsGdToIe(uint8_t *loc, const Relocation &rel, uint64_t val) {
+ if (rel.type == R_386_TLS_GD) {
+ // Convert (loc[-2] == 0x04)
+ // leal x@tlsgd(, %ebx, 1), %eax
+ // call ___tls_get_addr@plt
+ // or
+ // leal x@tlsgd(%reg), %eax
+ // call *___tls_get_addr@got(%reg)
+ const uint8_t inst[] = {
+ 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0, %eax
+ 0x03, 0x83, 0, 0, 0, 0, // addl x@gottpoff(%ebx), %eax
+ };
+ uint8_t *w = loc[-2] == 0x04 ? loc - 3 : loc - 2;
+ memcpy(w, inst, sizeof(inst));
+ write32le(w + 8, val);
+ } else if (rel.type == R_386_TLS_GOTDESC) {
+ // Convert leal x@tlsdesc(%ebx), %eax to movl x@gotntpoff(%ebx), %eax.
+ if (memcmp(loc - 2, "\x8d\x83", 2)) {
+ error(getErrorLocation(loc - 2) +
+ "R_386_TLS_GOTDESC must be used in leal x@tlsdesc(%ebx), %eax");
+ return;
+ }
+ loc[-2] = 0x8b;
+ write32le(loc, val);
+ } else {
+ // Convert call *x@tlsdesc(%eax) to xchg ax, ax.
+ assert(rel.type == R_386_TLS_DESC_CALL);
+ loc[0] = 0x66;
+ loc[1] = 0x90;
+ }
}
// In some conditions, relocations can be optimized to avoid using GOT.
// This function does that for Initial Exec to Local Exec case.
-void X86::relaxTlsIeToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const {
+static void relaxTlsIeToLe(uint8_t *loc, const Relocation &rel, uint64_t val) {
// Ulrich's document section 6.2 says that @gotntpoff can
// be used with MOVL or ADDL instructions.
// @indntpoff is similar to @gotntpoff, but for use in
write32le(loc, val);
}
-void X86::relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const {
+static void relaxTlsLdToLe(uint8_t *loc, const Relocation &rel, uint64_t val) {
if (rel.type == R_386_TLS_LDO_32) {
write32le(loc, val);
return;
}
+ if (loc[4] == 0xe8) {
+ // Convert
+ // leal x(%reg),%eax
+ // call ___tls_get_addr@plt
+ // to
+ const uint8_t inst[] = {
+ 0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0,%eax
+ 0x90, // nop
+ 0x8d, 0x74, 0x26, 0x00, // leal 0(%esi,1),%esi
+ };
+ memcpy(loc - 2, inst, sizeof(inst));
+ return;
+ }
+
// Convert
- // leal foo(%reg),%eax
- // call ___tls_get_addr
+ // leal x(%reg),%eax
+ // call *___tls_get_addr@got(%reg)
// to
- // movl %gs:0,%eax
- // nop
- // leal 0(%esi,1),%esi
const uint8_t inst[] = {
0x65, 0xa1, 0x00, 0x00, 0x00, 0x00, // movl %gs:0,%eax
- 0x90, // nop
- 0x8d, 0x74, 0x26, 0x00, // leal 0(%esi,1),%esi
+ 0x8d, 0xb6, 0x00, 0x00, 0x00, 0x00, // leal (%esi),%esi
};
memcpy(loc - 2, inst, sizeof(inst));
}
+void X86::relocateAlloc(InputSectionBase &sec, uint8_t *buf) const {
+ uint64_t secAddr = sec.getOutputSection()->addr;
+ if (auto *s = dyn_cast<InputSection>(&sec))
+ secAddr += s->outSecOff;
+ for (const Relocation &rel : sec.relocs()) {
+ uint8_t *loc = buf + rel.offset;
+ const uint64_t val = SignExtend64(
+ sec.getRelocTargetVA(sec.file, rel.type, rel.addend,
+ secAddr + rel.offset, *rel.sym, rel.expr),
+ 32);
+ switch (rel.expr) {
+ case R_RELAX_TLS_GD_TO_IE_GOTPLT:
+ relaxTlsGdToIe(loc, rel, val);
+ continue;
+ case R_RELAX_TLS_GD_TO_LE:
+ case R_RELAX_TLS_GD_TO_LE_NEG:
+ relaxTlsGdToLe(loc, rel, val);
+ continue;
+ case R_RELAX_TLS_LD_TO_LE:
+ relaxTlsLdToLe(loc, rel, val);
+ break;
+ case R_RELAX_TLS_IE_TO_LE:
+ relaxTlsIeToLe(loc, rel, val);
+ continue;
+ default:
+ relocate(loc, rel, val);
+ break;
+ }
+ }
+}
+
// If Intel Indirect Branch Tracking is enabled, we have to emit special PLT
// entries containing endbr32 instructions. A PLT entry will be split into two
// parts, one in .plt.sec (writePlt), and the other in .plt (writeIBTPlt).
void IntelIBT::writeGotPlt(uint8_t *buf, const Symbol &s) const {
uint64_t va =
- in.ibtPlt->getVA() + IBTPltHeaderSize + s.pltIndex * pltEntrySize;
+ in.ibtPlt->getVA() + IBTPltHeaderSize + s.getPltIdx() * pltEntrySize;
write32le(buf, va);
}
void RetpolinePic::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const {
- unsigned relOff = in.relaPlt->entsize * sym.pltIndex;
+ unsigned relOff = in.relaPlt->entsize * sym.getPltIdx();
const uint8_t insn[] = {
0x50, // pushl %eax
0x8b, 0x83, 0, 0, 0, 0, // mov foo@GOT(%ebx), %eax
void RetpolineNoPic::writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const {
- unsigned relOff = in.relaPlt->entsize * sym.pltIndex;
+ unsigned relOff = in.relaPlt->entsize * sym.getPltIdx();
const uint8_t insn[] = {
0x50, // 0: pushl %eax
0xa1, 0, 0, 0, 0, // 1: mov foo_in_GOT, %eax
tablegen(LLVM Options.inc -gen-opt-parser-defs)
add_public_tablegen_target(ELFOptionsTableGen)
+if(LLVM_ENABLE_ZLIB)
+ set(imported_libs ZLIB::ZLIB)
+endif()
+
+if(LLVM_ENABLE_ZSTD)
+ if(TARGET zstd::libzstd_shared AND NOT LLVM_USE_STATIC_ZSTD)
+ set(zstd_target zstd::libzstd_shared)
+ else()
+ set(zstd_target zstd::libzstd_static)
+ endif()
+endif()
+
+if(LLVM_ENABLE_ZSTD)
+ list(APPEND imported_libs ${zstd_target})
+endif()
+
add_lld_library(lldELF
AArch64ErrataFix.cpp
Arch/AArch64.cpp
Option
Passes
Support
+ TargetParser
LINK_LIBS
lldCommon
+ ${imported_libs}
${LLVM_PTHREAD_LIB}
DEPENDS
//===----------------------------------------------------------------------===//
#include "CallGraphSort.h"
-#include "OutputSections.h"
-#include "SymbolTable.h"
+#include "InputFiles.h"
+#include "InputSection.h"
#include "Symbols.h"
+#include "llvm/Support/FileSystem.h"
#include <numeric>
// Create the graph.
for (std::pair<SectionPair, uint64_t> &c : profile) {
- const auto *fromSB = cast<InputSectionBase>(c.first.first->repl);
- const auto *toSB = cast<InputSectionBase>(c.first.second->repl);
+ const auto *fromSB = cast<InputSectionBase>(c.first.first);
+ const auto *toSB = cast<InputSectionBase>(c.first.second);
uint64_t weight = c.second;
// Ignore edges between input sections belonging to different output
// Find the leader of V's belonged cluster (represented as an equivalence
// class). We apply union-find path-halving technique (simple to implement) in
// the meantime as it decreases depths and the time complexity.
-static int getLeader(std::vector<int> &leaders, int v) {
+static int getLeader(int *leaders, int v) {
while (leaders[v] != v) {
leaders[v] = leaders[leaders[v]];
v = leaders[v];
// then sort the clusters by density.
DenseMap<const InputSectionBase *, int> CallGraphSort::run() {
std::vector<int> sorted(clusters.size());
- std::vector<int> leaders(clusters.size());
+ std::unique_ptr<int[]> leaders(new int[clusters.size()]);
- std::iota(leaders.begin(), leaders.end(), 0);
+ std::iota(leaders.get(), leaders.get() + clusters.size(), 0);
std::iota(sorted.begin(), sorted.end(), 0);
llvm::stable_sort(sorted, [&](int a, int b) {
return clusters[a].getDensity() > clusters[b].getDensity();
if (c.bestPred.from == -1 || c.bestPred.weight * 10 <= c.initialWeight)
continue;
- int predL = getLeader(leaders, c.bestPred.from);
+ int predL = getLeader(leaders.get(), c.bestPred.from);
if (l == predL)
continue;
return orderMap;
}
-// Sort sections by the profile data provided by -callgraph-profile-file
+// Sort sections by the profile data provided by --callgraph-profile-file.
//
// This first builds a call graph based on the profile data then merges sections
// according to the C³ heuristic. All clusters are then sorted by a density
#include "llvm/ADT/DenseMap.h"
-namespace lld {
-namespace elf {
+namespace lld::elf {
class InputSectionBase;
llvm::DenseMap<const InputSectionBase *, int> computeCallGraphProfileOrder();
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
//
//===----------------------------------------------------------------------===//
//
-// The -gdb-index option instructs the linker to emit a .gdb_index section.
+// The --gdb-index option instructs the linker to emit a .gdb_index section.
// The section contains information to make gdb startup faster.
// The format of the section is described at
// https://sourceware.org/gdb/onlinedocs/gdb/Index-Section-Format.html.
//===----------------------------------------------------------------------===//
#include "DWARF.h"
+#include "InputSection.h"
#include "Symbols.h"
-#include "Target.h"
#include "lld/Common/Memory.h"
#include "llvm/DebugInfo/DWARF/DWARFDebugPubTable.h"
#include "llvm/Object/ELFObjectFile.h"
template <class ELFT> LLDDwarfObj<ELFT>::LLDDwarfObj(ObjFile<ELFT> *obj) {
// Get the ELF sections to retrieve sh_flags. See the SHF_GROUP comment below.
- ArrayRef<typename ELFT::Shdr> objSections =
- CHECK(obj->getObj().sections(), obj);
+ ArrayRef<typename ELFT::Shdr> objSections = obj->template getELFShdrs<ELFT>();
assert(objSections.size() == obj->getSections().size());
- for (auto it : llvm::enumerate(obj->getSections())) {
- InputSectionBase *sec = it.value();
+ for (auto [i, sec] : llvm::enumerate(obj->getSections())) {
if (!sec)
continue;
.Case(".debug_str_offsets", &strOffsetsSection)
.Case(".debug_line", &lineSection)
.Default(nullptr)) {
- m->Data = toStringRef(sec->data());
+ m->Data = toStringRef(sec->contentMaybeDecompress());
m->sec = sec;
continue;
}
if (sec->name == ".debug_abbrev")
- abbrevSection = toStringRef(sec->data());
+ abbrevSection = toStringRef(sec->contentMaybeDecompress());
else if (sec->name == ".debug_str")
- strSection = toStringRef(sec->data());
+ strSection = toStringRef(sec->contentMaybeDecompress());
else if (sec->name == ".debug_line_str")
- lineStrSection = toStringRef(sec->data());
+ lineStrSection = toStringRef(sec->contentMaybeDecompress());
else if (sec->name == ".debug_info" &&
- !(objSections[it.index()].sh_flags & ELF::SHF_GROUP)) {
+ !(objSections[i].sh_flags & ELF::SHF_GROUP)) {
// In DWARF v5, -fdebug-types-section places type units in .debug_info
// sections in COMDAT groups. They are not compile units and thus should
// be ignored for .gdb_index/diagnostics purposes.
// need to perform a lightweight parsing. We drop the SHF_GROUP flag when
// the InputSection was created, so we need to retrieve sh_flags from the
// associated ELF section header.
- infoSection.Data = toStringRef(sec->data());
+ infoSection.Data = toStringRef(sec->contentMaybeDecompress());
infoSection.sec = sec;
}
}
// to llvm since it has no idea about InputSection.
template <class ELFT>
template <class RelTy>
-Optional<RelocAddrEntry>
+std::optional<RelocAddrEntry>
LLDDwarfObj<ELFT>::findAux(const InputSectionBase &sec, uint64_t pos,
ArrayRef<RelTy> rels) const {
auto it =
partition_point(rels, [=](const RelTy &a) { return a.r_offset < pos; });
if (it == rels.end() || it->r_offset != pos)
- return None;
+ return std::nullopt;
const RelTy &rel = *it;
const ObjFile<ELFT> *file = sec.getFile<ELFT>();
DataRefImpl d;
d.p = getAddend<ELFT>(rel);
return RelocAddrEntry{secIndex, RelocationRef(d, nullptr),
- val, Optional<object::RelocationRef>(),
+ val, std::optional<object::RelocationRef>(),
0, LLDRelocationResolver<RelTy>::resolve};
}
template <class ELFT>
-Optional<RelocAddrEntry> LLDDwarfObj<ELFT>::find(const llvm::DWARFSection &s,
- uint64_t pos) const {
+std::optional<RelocAddrEntry>
+LLDDwarfObj<ELFT>::find(const llvm::DWARFSection &s, uint64_t pos) const {
auto &sec = static_cast<const LLDDWARFSection &>(s);
- if (sec.sec->areRelocsRela)
- return findAux(*sec.sec, pos, sec.sec->template relas<ELFT>());
- return findAux(*sec.sec, pos, sec.sec->template rels<ELFT>());
+ const RelsOrRelas<ELFT> rels = sec.sec->template relsOrRelas<ELFT>();
+ if (rels.areRelocsRel())
+ return findAux(*sec.sec, pos, rels.rels);
+ return findAux(*sec.sec, pos, rels.relas);
}
template class elf::LLDDwarfObj<ELF32LE>;
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/Object/ELF.h"
-namespace lld {
-namespace elf {
+namespace lld::elf {
class InputSection;
return ELFT::TargetEndianness == llvm::support::little;
}
- llvm::Optional<llvm::RelocAddrEntry> find(const llvm::DWARFSection &sec,
- uint64_t pos) const override;
+ std::optional<llvm::RelocAddrEntry> find(const llvm::DWARFSection &sec,
+ uint64_t pos) const override;
private:
template <class RelTy>
- llvm::Optional<llvm::RelocAddrEntry> findAux(const InputSectionBase &sec,
- uint64_t pos,
- ArrayRef<RelTy> rels) const;
+ std::optional<llvm::RelocAddrEntry> findAux(const InputSectionBase &sec,
+ uint64_t pos,
+ ArrayRef<RelTy> rels) const;
LLDDWARFSection gnuPubnamesSection;
LLDDWARFSection gnuPubtypesSection;
StringRef lineStrSection;
};
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
#ifndef LLD_ELF_DRIVER_H
#define LLD_ELF_DRIVER_H
-#include "LTO.h"
-#include "SymbolTable.h"
#include "lld/Common/LLVM.h"
-#include "lld/Common/Reproduce.h"
-#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringRef.h"
-#include "llvm/ADT/StringSet.h"
#include "llvm/Option/ArgList.h"
-#include "llvm/Support/raw_ostream.h"
-
-namespace lld {
-namespace elf {
-
-extern class LinkerDriver *driver;
-
-class LinkerDriver {
-public:
- void linkerMain(ArrayRef<const char *> args);
- void addFile(StringRef path, bool withLOption);
- void addLibrary(StringRef name);
-
-private:
- void createFiles(llvm::opt::InputArgList &args);
- void inferMachineType();
- template <class ELFT> void link(llvm::opt::InputArgList &args);
- template <class ELFT> void compileBitcodeFiles();
-
- // True if we are in --whole-archive and --no-whole-archive.
- bool inWholeArchive = false;
-
- // True if we are in --start-lib and --end-lib.
- bool inLib = false;
-
- // For LTO.
- std::unique_ptr<BitcodeCompiler> lto;
-
- std::vector<InputFile *> files;
-};
+#include <optional>
+namespace lld::elf {
// Parses command line options.
-class ELFOptTable : public llvm::opt::OptTable {
+class ELFOptTable : public llvm::opt::GenericOptTable {
public:
ELFOptTable();
llvm::opt::InputArgList parse(ArrayRef<const char *> argv);
void printHelp();
std::string createResponseFile(const llvm::opt::InputArgList &args);
-llvm::Optional<std::string> findFromSearchPaths(StringRef path);
-llvm::Optional<std::string> searchScript(StringRef path);
-llvm::Optional<std::string> searchLibraryBaseName(StringRef path);
-llvm::Optional<std::string> searchLibrary(StringRef path);
+std::optional<std::string> findFromSearchPaths(StringRef path);
+std::optional<std::string> searchScript(StringRef path);
+std::optional<std::string> searchLibraryBaseName(StringRef path);
+std::optional<std::string> searchLibrary(StringRef path);
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
class EhReader {
public:
EhReader(InputSectionBase *s, ArrayRef<uint8_t> d) : isec(s), d(d) {}
- size_t readEhRecordSize();
uint8_t getFdeEncoding();
bool hasLSDA();
private:
template <class P> void failOn(const P *loc, const Twine &msg) {
fatal("corrupted .eh_frame: " + msg + "\n>>> defined in " +
- isec->getObjMsg((const uint8_t *)loc - isec->data().data()));
+ isec->getObjMsg((const uint8_t *)loc - isec->content().data()));
}
uint8_t readByte();
};
}
-size_t elf::readEhRecordSize(InputSectionBase *s, size_t off) {
- return EhReader(s, s->data().slice(off)).readEhRecordSize();
-}
-
-// .eh_frame section is a sequence of records. Each record starts with
-// a 4 byte length field. This function reads the length.
-size_t EhReader::readEhRecordSize() {
- if (d.size() < 4)
- failOn(d.data(), "CIE/FDE too small");
-
- // First 4 bytes of CIE/FDE is the size of the record.
- // If it is 0xFFFFFFFF, the next 8 bytes contain the size instead,
- // but we do not support that format yet.
- uint64_t v = read32(d.data());
- if (v == UINT32_MAX)
- failOn(d.data(), "CIE/FDE too large");
- uint64_t size = v + 4;
- if (size > d.size())
- failOn(d.data(), "CIE/FDE ends past the end of the section");
- return size;
-}
-
// Read a byte and advance D by one byte.
uint8_t EhReader::readByte() {
if (d.empty())
readByte();
else if (c == 'P')
skipAugP();
- else if (c != 'B' && c != 'S')
+ else if (c != 'B' && c != 'S' && c != 'G')
failOn(aug.data(), "unknown .eh_frame augmentation string: " + aug);
}
return DW_EH_PE_absptr;
skipAugP();
else if (c == 'R')
readByte();
- else if (c != 'B' && c != 'S')
+ else if (c != 'B' && c != 'S' && c != 'G')
failOn(aug.data(), "unknown .eh_frame augmentation string: " + aug);
}
return false;
#include "lld/Common/LLVM.h"
-namespace lld {
-namespace elf {
-class InputSectionBase;
+namespace lld::elf {
struct EhSectionPiece;
-size_t readEhRecordSize(InputSectionBase *s, size_t off);
uint8_t getFdeEncoding(EhSectionPiece *p);
bool hasLSDA(const EhSectionPiece &p);
-} // namespace elf
-} // namespace lld
+}
#endif
#include "ICF.h"
#include "Config.h"
-#include "EhFrame.h"
+#include "InputFiles.h"
#include "LinkerScript.h"
#include "OutputSections.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
-#include "Writer.h"
-#include "llvm/ADT/StringExtras.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Object/ELF.h"
#include "llvm/Support/Parallel.h"
void forEachClass(llvm::function_ref<void(size_t, size_t)> fn);
- std::vector<InputSection *> sections;
+ SmallVector<InputSection *, 0> sections;
// We repeat the main loop while `Repeat` is true.
std::atomic<bool> repeat;
template <class RelTy>
bool ICF<ELFT>::constantEq(const InputSection *secA, ArrayRef<RelTy> ra,
const InputSection *secB, ArrayRef<RelTy> rb) {
+ if (ra.size() != rb.size())
+ return false;
for (size_t i = 0; i < ra.size(); ++i) {
if (ra[i].r_offset != rb[i].r_offset ||
ra[i].getType(config->isMips64EL) != rb[i].getType(config->isMips64EL))
// except relocation targets.
template <class ELFT>
bool ICF<ELFT>::equalsConstant(const InputSection *a, const InputSection *b) {
- if (a->numRelocations != b->numRelocations || a->flags != b->flags ||
- a->getSize() != b->getSize() || a->data() != b->data())
+ if (a->flags != b->flags || a->getSize() != b->getSize() ||
+ a->content() != b->content())
return false;
// If two sections have different output sections, we cannot merge them.
if (a->getParent() != b->getParent())
return false;
- if (a->areRelocsRela)
- return constantEq(a, a->template relas<ELFT>(), b,
- b->template relas<ELFT>());
- return constantEq(a, a->template rels<ELFT>(), b, b->template rels<ELFT>());
+ const RelsOrRelas<ELFT> ra = a->template relsOrRelas<ELFT>();
+ const RelsOrRelas<ELFT> rb = b->template relsOrRelas<ELFT>();
+ return ra.areRelocsRel() || rb.areRelocsRel()
+ ? constantEq(a, ra.rels, b, rb.rels)
+ : constantEq(a, ra.relas, b, rb.relas);
}
// Compare two lists of relocations. Returns true if all pairs of
// Compare "moving" part of two InputSections, namely relocation targets.
template <class ELFT>
bool ICF<ELFT>::equalsVariable(const InputSection *a, const InputSection *b) {
- if (a->areRelocsRela)
- return variableEq(a, a->template relas<ELFT>(), b,
- b->template relas<ELFT>());
- return variableEq(a, a->template rels<ELFT>(), b, b->template rels<ELFT>());
+ const RelsOrRelas<ELFT> ra = a->template relsOrRelas<ELFT>();
+ const RelsOrRelas<ELFT> rb = b->template relsOrRelas<ELFT>();
+ return ra.areRelocsRel() || rb.areRelocsRel()
+ ? variableEq(a, ra.rels, b, rb.rels)
+ : variableEq(a, ra.relas, b, rb.relas);
}
template <class ELFT> size_t ICF<ELFT>::findBoundary(size_t begin, size_t end) {
boundaries[0] = 0;
boundaries[numShards] = sections.size();
- parallelForEachN(1, numShards, [&](size_t i) {
+ parallelFor(1, numShards, [&](size_t i) {
boundaries[i] = findBoundary((i - 1) * step, sections.size());
});
- parallelForEachN(1, numShards + 1, [&](size_t i) {
+ parallelFor(1, numShards + 1, [&](size_t i) {
if (boundaries[i - 1] < boundaries[i])
forEachClassRange(boundaries[i - 1], boundaries[i], fn);
});
// Compute isPreemptible early. We may add more symbols later, so this loop
// cannot be merged with the later computeIsPreemptible() pass which is used
// by scanRelocations().
- for (Symbol *sym : symtab->symbols())
- sym->isPreemptible = computeIsPreemptible(*sym);
+ if (config->hasDynSymTab)
+ for (Symbol *sym : symtab.getSymbols())
+ sym->isPreemptible = computeIsPreemptible(*sym);
// Two text sections may have identical content and relocations but different
// LSDA, e.g. the two functions may have catch blocks of different types. If a
[&](InputSection &s) { s.eqClass[0] = s.eqClass[1] = ++uniqueId; });
// Collect sections to merge.
- for (InputSectionBase *sec : inputSections) {
- auto *s = cast<InputSection>(sec);
- if (s->eqClass[0] == 0) {
+ for (InputSectionBase *sec : ctx.inputSections) {
+ auto *s = dyn_cast<InputSection>(sec);
+ if (s && s->eqClass[0] == 0) {
if (isEligible(s))
sections.push_back(s);
else
// Initially, we use hash values to partition sections.
parallelForEach(sections, [&](InputSection *s) {
// Set MSB to 1 to avoid collisions with unique IDs.
- s->eqClass[0] = xxHash64(s->data()) | (1U << 31);
+ s->eqClass[0] = xxHash64(s->content()) | (1U << 31);
});
// Perform 2 rounds of relocation hash propagation. 2 is an empirical value to
// a large time complexity will have less work to do.
for (unsigned cnt = 0; cnt != 2; ++cnt) {
parallelForEach(sections, [&](InputSection *s) {
- if (s->areRelocsRela)
- combineRelocHashes<ELFT>(cnt, s, s->template relas<ELFT>());
+ const RelsOrRelas<ELFT> rels = s->template relsOrRelas<ELFT>();
+ if (rels.areRelocsRel())
+ combineRelocHashes<ELFT>(cnt, s, rels.rels);
else
- combineRelocHashes<ELFT>(cnt, s, s->template rels<ELFT>());
+ combineRelocHashes<ELFT>(cnt, s, rels.relas);
});
}
}
});
+ // Change Defined symbol's section field to the canonical one.
+ auto fold = [](Symbol *sym) {
+ if (auto *d = dyn_cast<Defined>(sym))
+ if (auto *sec = dyn_cast_or_null<InputSection>(d->section))
+ if (sec->repl != d->section) {
+ d->section = sec->repl;
+ d->folded = true;
+ }
+ };
+ for (Symbol *sym : symtab.getSymbols())
+ fold(sym);
+ parallelForEach(ctx.objectFiles, [&](ELFFileBase *file) {
+ for (Symbol *sym : file->getLocalSymbols())
+ fold(sym);
+ });
+
// InputSectionDescription::sections is populated by processSectionCommands().
// ICF may fold some input sections assigned to output sections. Remove them.
- for (BaseCommand *base : script->sectionCommands)
- if (auto *sec = dyn_cast<OutputSection>(base))
- for (BaseCommand *sub_base : sec->sectionCommands)
- if (auto *isd = dyn_cast<InputSectionDescription>(sub_base))
+ for (SectionCommand *cmd : script->sectionCommands)
+ if (auto *osd = dyn_cast<OutputDesc>(cmd))
+ for (SectionCommand *subCmd : osd->osec.commands)
+ if (auto *isd = dyn_cast<InputSectionDescription>(subCmd))
llvm::erase_if(isd->sections,
[](InputSection *isec) { return !isec->isLive(); });
}
#ifndef LLD_ELF_ICF_H
#define LLD_ELF_ICF_H
-namespace lld {
-namespace elf {
+namespace lld::elf {
template <class ELFT> void doIcf();
-} // namespace elf
-} // namespace lld
+}
#endif
#ifndef LLD_ELF_INPUT_SECTION_H
#define LLD_ELF_INPUT_SECTION_H
-#include "Config.h"
#include "Relocations.h"
-#include "Thunks.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/LLVM.h"
+#include "lld/Common/Memory.h"
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/TinyPtrVector.h"
#include "llvm/Object/ELF.h"
+#include "llvm/Support/Compiler.h"
namespace lld {
namespace elf {
+class InputFile;
class Symbol;
-struct SectionPiece;
class Defined;
struct Partition;
class SyntheticSection;
-class MergeSyntheticSection;
template <class ELFT> class ObjFile;
class OutputSection;
-extern std::vector<Partition> partitions;
+LLVM_LIBRARY_VISIBILITY extern std::vector<Partition> partitions;
+
+// Returned by InputSectionBase::relsOrRelas. At least one member is empty.
+template <class ELFT> struct RelsOrRelas {
+ ArrayRef<typename ELFT::Rel> rels;
+ ArrayRef<typename ELFT::Rela> relas;
+ bool areRelocsRel() const { return rels.size(); }
+};
// This is the base class of all sections that lld handles. Some are sections in
// input files, some are sections in the produced output file and some exist
// sections.
class SectionBase {
public:
- enum Kind { Regular, EHFrame, Merge, Synthetic, Output };
+ enum Kind { Regular, Synthetic, EHFrame, Merge, Output };
Kind kind() const { return (Kind)sectionKind; }
- StringRef name;
-
- // This pointer points to the "real" instance of this instance.
- // Usually Repl == this. However, if ICF merges two sections,
- // Repl pointer of one section points to another section. So,
- // if you need to get a pointer to this instance, do not use
- // this but instead this->Repl.
- SectionBase *repl;
-
uint8_t sectionKind : 3;
// The next two bit fields are only used by InputSectionBase, but we
// Set for sections that should not be folded by ICF.
uint8_t keepUnique : 1;
+ uint8_t partition = 1;
+ uint32_t type;
+ StringRef name;
+
// The 1-indexed partition that this section is assigned to by the garbage
// collector, or 0 if this section is dead. Normally there is only one
// partition, so this will either be 0 or 1.
- uint8_t partition;
elf::Partition &getPartition() const;
// These corresponds to the fields in Elf_Shdr.
- uint32_t alignment;
uint64_t flags;
- uint64_t entsize;
- uint32_t type;
+ uint32_t addralign;
+ uint32_t entsize;
uint32_t link;
uint32_t info;
void markDead() { partition = 0; }
protected:
- SectionBase(Kind sectionKind, StringRef name, uint64_t flags,
- uint64_t entsize, uint64_t alignment, uint32_t type,
- uint32_t info, uint32_t link)
- : name(name), repl(this), sectionKind(sectionKind), bss(false),
- keepUnique(false), partition(0), alignment(alignment), flags(flags),
- entsize(entsize), type(type), link(link), info(info) {}
+ constexpr SectionBase(Kind sectionKind, StringRef name, uint64_t flags,
+ uint32_t entsize, uint32_t addralign, uint32_t type,
+ uint32_t info, uint32_t link)
+ : sectionKind(sectionKind), bss(false), keepUnique(false), type(type),
+ name(name), flags(flags), addralign(addralign), entsize(entsize),
+ link(link), info(info) {}
};
+struct RISCVRelaxAux;
+
// This corresponds to a section of an input file.
class InputSectionBase : public SectionBase {
public:
InputSectionBase(InputFile *file, uint64_t flags, uint32_t type,
uint64_t entsize, uint32_t link, uint32_t info,
- uint32_t alignment, ArrayRef<uint8_t> data, StringRef name,
+ uint32_t addralign, ArrayRef<uint8_t> data, StringRef name,
Kind sectionKind);
static bool classof(const SectionBase *s) { return s->kind() != Output; }
- // Relocations that refer to this section.
- unsigned numRelocations : 31;
- unsigned areRelocsRela : 1;
- const void *firstRelocation = nullptr;
-
// The file which contains this section. Its dynamic type is always
// ObjFile<ELFT>, but in order to avoid ELFT, we use InputFile as
// its static type.
InputFile *file;
+ // Input sections are part of an output section. Special sections
+ // like .eh_frame and merge sections are first combined into a
+ // synthetic section that is then added to an output section. In all
+ // cases this points one level up.
+ SectionBase *parent = nullptr;
+
+ // Section index of the relocation section if exists.
+ uint32_t relSecIdx = 0;
+
template <class ELFT> ObjFile<ELFT> *getFile() const {
return cast_or_null<ObjFile<ELFT>>(file);
}
- // If basic block sections are enabled, many code sections could end up with
- // one or two jump instructions at the end that could be relaxed to a smaller
- // instruction. The members below help trimming the trailing jump instruction
- // and shrinking a section.
- unsigned bytesDropped = 0;
+ // Used by --optimize-bb-jumps and RISC-V linker relaxation temporarily to
+ // indicate the number of bytes which is not counted in the size. This should
+ // be reset to zero after uses.
+ uint16_t bytesDropped = 0;
+
+ mutable bool compressed = false;
// Whether the section needs to be padded with a NOP filler due to
// deleteFallThruJmpInsn.
bool nopFiller = false;
- void drop_back(uint64_t num) { bytesDropped += num; }
+ void drop_back(unsigned num) {
+ assert(bytesDropped + num < 256);
+ bytesDropped += num;
+ }
void push_back(uint64_t num) {
assert(bytesDropped >= num);
bytesDropped -= num;
}
+ mutable const uint8_t *content_;
+ uint64_t size;
+
void trim() {
if (bytesDropped) {
- rawData = rawData.drop_back(bytesDropped);
+ size -= bytesDropped;
bytesDropped = 0;
}
}
- ArrayRef<uint8_t> data() const {
- if (uncompressedSize >= 0)
- uncompress();
- return rawData;
+ ArrayRef<uint8_t> content() const {
+ return ArrayRef<uint8_t>(content_, size);
+ }
+ ArrayRef<uint8_t> contentMaybeDecompress() const {
+ if (compressed)
+ decompress();
+ return content();
}
-
- uint64_t getOffsetInFile() const;
-
- // Input sections are part of an output section. Special sections
- // like .eh_frame and merge sections are first combined into a
- // synthetic section that is then added to an output section. In all
- // cases this points one level up.
- SectionBase *parent = nullptr;
// The next member in the section group if this section is in a group. This is
// used by --gc-sections.
InputSectionBase *nextInSectionGroup = nullptr;
- template <class ELFT> ArrayRef<typename ELFT::Rel> rels() const {
- assert(!areRelocsRela);
- return llvm::makeArrayRef(
- static_cast<const typename ELFT::Rel *>(firstRelocation),
- numRelocations);
- }
-
- template <class ELFT> ArrayRef<typename ELFT::Rela> relas() const {
- assert(areRelocsRela);
- return llvm::makeArrayRef(
- static_cast<const typename ELFT::Rela *>(firstRelocation),
- numRelocations);
- }
+ template <class ELFT> RelsOrRelas<ELFT> relsOrRelas() const;
// InputSections that are dependent on us (reverse dependency for GC)
llvm::TinyPtrVector<InputSection *> dependentSections;
// Get the function symbol that encloses this offset from within the
// section.
- template <class ELFT>
Defined *getEnclosingFunction(uint64_t offset);
// Returns a source location string. Used to construct an error message.
- template <class ELFT> std::string getLocation(uint64_t offset);
+ std::string getLocation(uint64_t offset);
std::string getSrcMsg(const Symbol &sym, uint64_t offset);
std::string getObjMsg(uint64_t offset);
// relocations, assuming that Buf points to this section's copy in
// the mmap'ed output buffer.
template <class ELFT> void relocate(uint8_t *buf, uint8_t *bufEnd);
- void relocateAlloc(uint8_t *buf, uint8_t *bufEnd);
static uint64_t getRelocTargetVA(const InputFile *File, RelType Type,
int64_t A, uint64_t P, const Symbol &Sym,
RelExpr Expr);
// This vector contains such "cooked" relocations.
SmallVector<Relocation, 0> relocations;
- // These are modifiers to jump instructions that are necessary when basic
- // block sections are enabled. Basic block sections creates opportunities to
- // relax jump instructions at basic block boundaries after reordering the
- // basic blocks.
- SmallVector<JumpInstrMod, 0> jumpInstrMods;
+ void addReloc(const Relocation &r) { relocations.push_back(r); }
+ MutableArrayRef<Relocation> relocs() { return relocations; }
+ ArrayRef<Relocation> relocs() const { return relocations; }
+
+ union {
+ // These are modifiers to jump instructions that are necessary when basic
+ // block sections are enabled. Basic block sections creates opportunities
+ // to relax jump instructions at basic block boundaries after reordering the
+ // basic blocks.
+ JumpInstrMod *jumpInstrMod = nullptr;
+
+ // Auxiliary information for RISC-V linker relaxation. RISC-V does not use
+ // jumpInstrMod.
+ RISCVRelaxAux *relaxAux;
+
+ // The compressed content size when `compressed` is true.
+ size_t compressedSize;
+ };
// A function compiled with -fsplit-stack calling a function
// compiled without -fsplit-stack needs its prologue adjusted. Find
template <typename T> llvm::ArrayRef<T> getDataAs() const {
- size_t s = data().size();
+ size_t s = content().size();
assert(s % sizeof(T) == 0);
- return llvm::makeArrayRef<T>((const T *)data().data(), s / sizeof(T));
+ return llvm::ArrayRef<T>((const T *)content().data(), s / sizeof(T));
}
protected:
template <typename ELFT>
void parseCompressedHeader();
- void uncompress() const;
-
- mutable ArrayRef<uint8_t> rawData;
-
- // This field stores the uncompressed size of the compressed data in rawData,
- // or -1 if rawData is not compressed (either because the section wasn't
- // compressed in the first place, or because we ended up uncompressing it).
- // Since the feature is not used often, this is usually -1.
- mutable int64_t uncompressedSize = -1;
+ void decompress() const;
};
// SectionPiece represents a piece of splittable section contents.
// have to be as compact as possible, which is why we don't store the size (can
// be found by looking at the next one).
struct SectionPiece {
+ SectionPiece() = default;
SectionPiece(size_t off, uint32_t hash, bool live)
- : inputOff(off), live(live || !config->gcSections), hash(hash >> 1) {}
+ : inputOff(off), live(live), hash(hash >> 1) {}
uint32_t inputOff;
uint32_t live : 1;
// Splittable sections are handled as a sequence of data
// rather than a single large blob of data.
- std::vector<SectionPiece> pieces;
+ SmallVector<SectionPiece, 0> pieces;
// Returns I'th piece's data. This function is very hot when
// string merging is enabled, so we want to inline.
llvm::CachedHashStringRef getData(size_t i) const {
size_t begin = pieces[i].inputOff;
size_t end =
- (pieces.size() - 1 == i) ? data().size() : pieces[i + 1].inputOff;
- return {toStringRef(data().slice(begin, end - begin)), pieces[i].hash};
+ (pieces.size() - 1 == i) ? content().size() : pieces[i + 1].inputOff;
+ return {toStringRef(content().slice(begin, end - begin)), pieces[i].hash};
}
// Returns the SectionPiece at a given input section offset.
- SectionPiece *getSectionPiece(uint64_t offset);
- const SectionPiece *getSectionPiece(uint64_t offset) const {
+ SectionPiece &getSectionPiece(uint64_t offset);
+ const SectionPiece &getSectionPiece(uint64_t offset) const {
return const_cast<MergeInputSection *>(this)->getSectionPiece(offset);
}
- SyntheticSection *getParent() const;
+ SyntheticSection *getParent() const {
+ return cast_or_null<SyntheticSection>(parent);
+ }
private:
- void splitStrings(ArrayRef<uint8_t> a, size_t size);
+ void splitStrings(StringRef s, size_t size);
void splitNonStrings(ArrayRef<uint8_t> a, size_t size);
};
: inputOff(off), sec(sec), size(size), firstRelocation(firstRelocation) {}
ArrayRef<uint8_t> data() const {
- return {sec->data().data() + this->inputOff, size};
+ return {sec->content().data() + this->inputOff, size};
}
size_t inputOff;
// Splittable sections are handled as a sequence of data
// rather than a single large blob of data.
- std::vector<EhSectionPiece> pieces;
+ SmallVector<EhSectionPiece, 0> cies, fdes;
SyntheticSection *getParent() const;
+ uint64_t getParentOffset(uint64_t offset) const;
};
// This is a section that is added directly to an output section
// .eh_frame. It also includes the synthetic sections themselves.
class InputSection : public InputSectionBase {
public:
- InputSection(InputFile *f, uint64_t flags, uint32_t type, uint32_t alignment,
+ InputSection(InputFile *f, uint64_t flags, uint32_t type, uint32_t addralign,
ArrayRef<uint8_t> data, StringRef name, Kind k = Regular);
template <class ELFT>
InputSection(ObjFile<ELFT> &f, const typename ELFT::Shdr &header,
StringRef name);
+ static bool classof(const SectionBase *s) {
+ return s->kind() == SectionBase::Regular ||
+ s->kind() == SectionBase::Synthetic;
+ }
+
// Write this section to a mmap'ed file, assuming Buf is pointing to
// beginning of the output section.
template <class ELFT> void writeTo(uint8_t *buf);
- uint64_t getOffset(uint64_t offset) const { return outSecOff + offset; }
-
- OutputSection *getParent() const;
+ OutputSection *getParent() const {
+ return reinterpret_cast<OutputSection *>(parent);
+ }
// This variable has two usages. Initially, it represents an index in the
// OutputSection's InputSection list, and is used when ordering SHF_LINK_ORDER
// the beginning of the output section this section was assigned to.
uint64_t outSecOff = 0;
- static bool classof(const SectionBase *s);
-
InputSectionBase *getRelocatedSection() const;
template <class ELFT, class RelTy>
void relocateNonAlloc(uint8_t *buf, llvm::ArrayRef<RelTy> rels);
+ // Points to the canonical section. If ICF folds two sections, repl pointer of
+ // one section points to the other.
+ InputSection *repl = this;
+
// Used by ICF.
uint32_t eqClass[2] = {0, 0};
template <class ELFT> void copyShtGroup(uint8_t *buf);
};
-#ifdef _WIN32
-static_assert(sizeof(InputSection) <= 192, "InputSection is too big");
-#else
-static_assert(sizeof(InputSection) <= 184, "InputSection is too big");
-#endif
+static_assert(sizeof(InputSection) <= 152, "InputSection is too big");
+
+class SyntheticSection : public InputSection {
+public:
+ SyntheticSection(uint64_t flags, uint32_t type, uint32_t addralign,
+ StringRef name)
+ : InputSection(nullptr, flags, type, addralign, {}, name,
+ InputSectionBase::Synthetic) {}
+
+ virtual ~SyntheticSection() = default;
+ virtual size_t getSize() const = 0;
+ virtual bool updateAllocSize() { return false; }
+ // If the section has the SHF_ALLOC flag and the size may be changed if
+ // thunks are added, update the section size.
+ virtual bool isNeeded() const { return true; }
+ virtual void finalizeContents() {}
+ virtual void writeTo(uint8_t *buf) = 0;
+
+ static bool classof(const SectionBase *sec) {
+ return sec->kind() == InputSectionBase::Synthetic;
+ }
+};
inline bool isDebugSection(const InputSectionBase &sec) {
return (sec.flags & llvm::ELF::SHF_ALLOC) == 0 &&
- (sec.name.startswith(".debug") || sec.name.startswith(".zdebug"));
+ sec.name.startswith(".debug");
}
-// The list of all input sections.
-extern std::vector<InputSectionBase *> inputSections;
-
// The set of TOC entries (.toc + addend) for which we should not apply
// toc-indirect to toc-relative relaxation. const Symbol * refers to the
// STT_SECTION symbol associated to the .toc input section.
#include "LTO.h"
#include "Config.h"
#include "InputFiles.h"
-#include "LinkerScript.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "lld/Common/Args.h"
#include "lld/Common/ErrorHandler.h"
+#include "lld/Common/Strings.h"
#include "lld/Common/TargetOptionsCommandFlags.h"
-#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/BinaryFormat/ELF.h"
-#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/Bitcode/BitcodeWriter.h"
-#include "llvm/IR/DiagnosticPrinter.h"
-#include "llvm/LTO/Caching.h"
#include "llvm/LTO/Config.h"
#include "llvm/LTO/LTO.h"
-#include "llvm/Object/SymbolicFile.h"
+#include "llvm/Support/Caching.h"
#include "llvm/Support/CodeGen.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
// LLD supports the new relocations and address-significance tables.
c.Options = initTargetOptionsFromCodeGenFlags();
- c.Options.RelaxELFRelocations = true;
c.Options.EmitAddrsig = true;
+ for (StringRef C : config->mllvmOpts)
+ c.MllvmArgs.emplace_back(C.str());
// Always emit a section per function/datum with LTO.
c.Options.FunctionSections = true;
}
}
- c.Options.PseudoProbeForProfiling = config->ltoPseudoProbeForProfiling;
c.Options.UniqueBasicBlockSectionNames =
config->ltoUniqueBasicBlockSectionNames;
if (auto relocModel = getRelocModelFromCMModel())
c.RelocModel = *relocModel;
else if (config->relocatable)
- c.RelocModel = None;
+ c.RelocModel = std::nullopt;
else if (config->isPic)
c.RelocModel = Reloc::PIC_;
else
c.RemarksHotnessThreshold = config->optRemarksHotnessThreshold;
c.RemarksFormat = std::string(config->optRemarksFormat);
+ // Set up output file to emit statistics.
+ c.StatsFile = std::string(config->optStatsFilename);
+
c.SampleProfile = std::string(config->ltoSampleProfile);
- c.UseNewPM = config->ltoNewPassManager;
+ for (StringRef pluginFn : config->passPlugins)
+ c.PassPlugins.push_back(std::string(pluginFn));
c.DebugPassManager = config->ltoDebugPassManager;
c.DwoDir = std::string(config->dwoDir);
c.CSIRProfile = std::string(config->ltoCSProfileFile);
c.RunCSIRInstr = config->ltoCSProfileGenerate;
+ c.PGOWarnMismatch = config->ltoPGOWarnMismatch;
+
+ c.OpaquePointers = config->opaquePointers;
if (config->emitLLVM) {
c.PostInternalizeModuleHook = [](size_t task, const Module &m) {
};
}
- if (config->ltoEmitAsm)
+ if (config->ltoEmitAsm) {
c.CGFileType = CGFT_AssemblyFile;
+ c.Options.MCOptions.AsmVerbose = true;
+ }
- if (config->saveTemps)
+ if (!config->saveTempsArgs.empty())
checkError(c.addSaveTemps(config->outputFile.str() + ".",
- /*UseInputModulePath*/ true));
+ /*UseInputModulePath*/ true,
+ config->saveTempsArgs));
return c;
}
// Initialize ltoObj.
lto::ThinBackend backend;
+ auto onIndexWrite = [&](StringRef s) { thinIndices.erase(s); };
if (config->thinLTOIndexOnly) {
- auto onIndexWrite = [&](StringRef s) { thinIndices.erase(s); };
backend = lto::createWriteIndexesThinBackend(
std::string(config->thinLTOPrefixReplace.first),
std::string(config->thinLTOPrefixReplace.second),
config->thinLTOEmitImportsFiles, indexFile.get(), onIndexWrite);
} else {
backend = lto::createInProcessThinBackend(
- llvm::heavyweight_hardware_concurrency(config->thinLTOJobs));
+ llvm::heavyweight_hardware_concurrency(config->thinLTOJobs),
+ onIndexWrite, config->thinLTOEmitIndexFiles,
+ config->thinLTOEmitImportsFiles);
}
ltoObj = std::make_unique<lto::LTO>(createConfig(), backend,
config->ltoPartitions);
// Initialize usedStartStop.
- for (Symbol *sym : symtab->symbols()) {
+ if (ctx.bitcodeFiles.empty())
+ return;
+ for (Symbol *sym : symtab.getSymbols()) {
+ if (sym->isPlaceholder())
+ continue;
StringRef s = sym->getName();
for (StringRef prefix : {"__start_", "__stop_"})
if (s.startswith(prefix))
lto::InputFile &obj = *f.obj;
bool isExec = !config->shared && !config->relocatable;
- if (config->thinLTOIndexOnly)
+ if (config->thinLTOEmitIndexFiles)
thinIndices.insert(obj.getName());
ArrayRef<Symbol *> syms = f.getSymbols();
// for doing final link.
// 2) Symbols that are used in regular objects.
// 3) C named sections if we have corresponding __start_/__stop_ symbol.
- // 4) Symbols that are defined in bitcode files and used for dynamic linking.
+ // 4) Symbols that are defined in bitcode files and used for dynamic
+ // linking.
+ // 5) Symbols that will be referenced after linker wrapping is performed.
r.VisibleToRegularObj = config->relocatable || sym->isUsedInRegularObj ||
+ sym->referencedAfterWrap ||
(r.Prevailing && sym->includeInDynsym()) ||
usedStartStop.count(objSym.getSectionName());
// Identify symbols exported dynamically, and that therefore could be
// referenced by a shared library not visible to the linker.
- r.ExportDynamic = sym->computeBinding() != STB_LOCAL &&
- (sym->isExportDynamic(sym->kind(), sym->visibility) ||
- sym->exportDynamic || sym->inDynamicList);
+ r.ExportDynamic =
+ sym->computeBinding() != STB_LOCAL &&
+ (config->exportDynamic || sym->exportDynamic || sym->inDynamicList);
const auto *dr = dyn_cast<Defined>(sym);
r.FinalDefinitionInLinkageUnit =
- (isExec || sym->visibility != STV_DEFAULT) && dr &&
+ (isExec || sym->visibility() != STV_DEFAULT) && dr &&
// Skip absolute symbols from ELF objects, otherwise PC-rel relocations
// will be generated by for them, triggering linker errors.
// Symbol section is always null for bitcode symbols, hence the check
!(dr->section == nullptr && (!sym->file || sym->file->isElf()));
if (r.Prevailing)
- sym->replace(Undefined{nullptr, sym->getName(), STB_GLOBAL, STV_DEFAULT,
- sym->type});
+ Undefined(nullptr, StringRef(), STB_GLOBAL, STV_DEFAULT, sym->type)
+ .overwrite(*sym);
// We tell LTO to not apply interprocedural optimization for wrapped
// (with --wrap) symbols because otherwise LTO would inline them while
// their values are still not final.
- r.LinkerRedefined = !sym->canInline;
+ r.LinkerRedefined = sym->scriptDefined;
}
checkError(ltoObj->add(std::move(f.obj), resols));
}
// This is needed because this is what GNU gold plugin does and we have a
// distributed build system that depends on that behavior.
static void thinLTOCreateEmptyIndexFiles() {
- for (LazyObjFile *f : lazyObjFiles) {
- if (f->fetched || !isBitcode(f->mb))
+ DenseSet<StringRef> linkedBitCodeFiles;
+ for (BitcodeFile *f : ctx.bitcodeFiles)
+ linkedBitCodeFiles.insert(f->getName());
+
+ for (BitcodeFile *f : ctx.lazyBitcodeFiles) {
+ if (!f->lazy)
+ continue;
+ if (linkedBitCodeFiles.contains(f->getName()))
continue;
- std::string path = replaceThinLTOSuffix(getThinLTOOutputFile(f->getName()));
+ std::string path =
+ replaceThinLTOSuffix(getThinLTOOutputFile(f->obj->getName()));
std::unique_ptr<raw_fd_ostream> os = openFile(path + ".thinlto.bc");
if (!os)
continue;
ModuleSummaryIndex m(/*HaveGVs*/ false);
m.setSkipModuleByDistributedBackend();
- WriteIndexToFile(m, *os);
+ writeIndexToFile(m, *os);
if (config->thinLTOEmitImportsFiles)
openFile(path + ".imports");
}
// The --thinlto-cache-dir option specifies the path to a directory in which
// to cache native object files for ThinLTO incremental builds. If a path was
// specified, configure LTO to use it as the cache directory.
- lto::NativeObjectCache cache;
+ FileCache cache;
if (!config->thinLTOCacheDir.empty())
- cache = check(
- lto::localCache(config->thinLTOCacheDir,
- [&](size_t task, std::unique_ptr<MemoryBuffer> mb) {
- files[task] = std::move(mb);
- }));
+ cache = check(localCache("ThinLTO", "Thin", config->thinLTOCacheDir,
+ [&](size_t task, const Twine &moduleName,
+ std::unique_ptr<MemoryBuffer> mb) {
+ files[task] = std::move(mb);
+ }));
- if (!bitcodeFiles.empty())
+ if (!ctx.bitcodeFiles.empty())
checkError(ltoObj->run(
- [&](size_t task) {
- return std::make_unique<lto::NativeObjectStream>(
+ [&](size_t task, const Twine &moduleName) {
+ return std::make_unique<CachedFileStream>(
std::make_unique<raw_svector_ostream>(buf[task]));
},
cache));
}
}
- if (config->thinLTOIndexOnly) {
+ if (config->thinLTOEmitIndexFiles)
thinLTOCreateEmptyIndexFiles();
+ if (config->thinLTOIndexOnly) {
if (!config->ltoObjPath.empty())
saveBuffer(buf[0], config->ltoObjPath);
}
if (!config->thinLTOCacheDir.empty())
- pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy);
+ pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy, files);
if (!config->ltoObjPath.empty()) {
saveBuffer(buf[0], config->ltoObjPath);
saveBuffer(buf[i], config->ltoObjPath + Twine(i));
}
- if (config->saveTemps) {
+ if (config->saveTempsArgs.contains("prelink")) {
if (!buf[0].empty())
saveBuffer(buf[0], config->outputFile + ".lto.o");
for (unsigned i = 1; i != maxTasks; ++i)
std::vector<InputFile *> ret;
for (unsigned i = 0; i != maxTasks; ++i)
if (!buf[i].empty())
- ret.push_back(createObjectFile(MemoryBufferRef(buf[i], "lto.tmp")));
+ ret.push_back(createObjFile(MemoryBufferRef(buf[i], "lto.tmp")));
for (std::unique_ptr<MemoryBuffer> &file : files)
if (file)
- ret.push_back(createObjectFile(*file));
+ ret.push_back(createObjFile(*file));
return ret;
}
#include <memory>
#include <vector>
-namespace llvm {
-namespace lto {
+namespace llvm::lto {
class LTO;
}
-} // namespace llvm
-namespace lld {
-namespace elf {
+namespace lld::elf {
class BitcodeFile;
class InputFile;
-class LazyObjFile;
class BitcodeCompiler {
public:
std::unique_ptr<llvm::raw_fd_ostream> indexFile;
llvm::DenseSet<StringRef> thinIndices;
};
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
#include "lld/Common/Strings.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Compiler.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
-#include <vector>
-namespace lld {
-namespace elf {
+namespace lld::elf {
class Defined;
class InputFile;
class InputSectionBase;
class OutputSection;
class SectionBase;
-class Symbol;
class ThunkSection;
+struct OutputDesc;
// This represents an r-value in the linker script.
struct ExprValue {
ExprValue(SectionBase *sec, bool forceAbsolute, uint64_t val,
const Twine &loc)
- : sec(sec), forceAbsolute(forceAbsolute), val(val), loc(loc.str()) {}
+ : sec(sec), val(val), forceAbsolute(forceAbsolute), loc(loc.str()) {}
ExprValue(uint64_t val) : ExprValue(nullptr, false, val, "") {}
// If a value is relative to a section, it has a non-null Sec.
SectionBase *sec;
- // True if this expression is enclosed in ABSOLUTE().
- // This flag affects the return value of getValue().
- bool forceAbsolute;
-
uint64_t val;
uint64_t alignment = 1;
// resets type to STT_NOTYPE.
uint8_t type = llvm::ELF::STT_NOTYPE;
+ // True if this expression is enclosed in ABSOLUTE().
+ // This flag affects the return value of getValue().
+ bool forceAbsolute;
+
// Original source location. Used for error messages.
std::string loc;
};
ByteKind // BYTE(expr), SHORT(expr), LONG(expr) or QUAD(expr)
};
-struct BaseCommand {
- BaseCommand(int k) : kind(k) {}
+struct SectionCommand {
+ SectionCommand(int k) : kind(k) {}
int kind;
};
// This represents ". = <expr>" or "<symbol> = <expr>".
-struct SymbolAssignment : BaseCommand {
+struct SymbolAssignment : SectionCommand {
SymbolAssignment(StringRef name, Expr e, std::string loc)
- : BaseCommand(AssignmentKind), name(name), expression(e), location(loc) {}
+ : SectionCommand(AssignmentKind), name(name), expression(e),
+ location(loc) {}
- static bool classof(const BaseCommand *c) {
+ static bool classof(const SectionCommand *c) {
return c->kind == AssignmentKind;
}
// MEMORY command.
struct MemoryRegion {
MemoryRegion(StringRef name, Expr origin, Expr length, uint32_t flags,
- uint32_t negFlags)
+ uint32_t invFlags, uint32_t negFlags, uint32_t negInvFlags)
: name(std::string(name)), origin(origin), length(length), flags(flags),
- negFlags(negFlags) {}
+ invFlags(invFlags), negFlags(negFlags), negInvFlags(negInvFlags) {}
std::string name;
Expr origin;
Expr length;
+ // A section can be assigned to the region if any of these ELF section flags
+ // are set...
uint32_t flags;
+ // ... or any of these flags are not set.
+ // For example, the memory region attribute "r" maps to SHF_WRITE.
+ uint32_t invFlags;
+ // A section cannot be assigned to the region if any of these ELF section
+ // flags are set...
uint32_t negFlags;
+ // ... or any of these flags are not set.
+ // For example, the memory region attribute "!r" maps to SHF_WRITE.
+ uint32_t negInvFlags;
uint64_t curPos = 0;
+
+ bool compatibleWith(uint32_t secFlags) const {
+ if ((secFlags & negFlags) || (~secFlags & negInvFlags))
+ return false;
+ return (secFlags & flags) || (~secFlags & invFlags);
+ }
};
// This struct represents one section match pattern in SECTIONS() command.
StringMatcher excludedFilePat;
// Cache of the most recent input argument and result of excludesFile().
- mutable llvm::Optional<std::pair<const InputFile *, bool>> excludesFileCache;
+ mutable std::optional<std::pair<const InputFile *, bool>> excludesFileCache;
public:
SectionPattern(StringMatcher &&pat1, StringMatcher &&pat2)
SortSectionPolicy sortInner;
};
-class InputSectionDescription : public BaseCommand {
+class InputSectionDescription : public SectionCommand {
SingleStringMatcher filePat;
// Cache of the most recent input argument and result of matchesFile().
- mutable llvm::Optional<std::pair<const InputFile *, bool>> matchesFileCache;
+ mutable std::optional<std::pair<const InputFile *, bool>> matchesFileCache;
public:
InputSectionDescription(StringRef filePattern, uint64_t withFlags = 0,
uint64_t withoutFlags = 0)
- : BaseCommand(InputSectionKind), filePat(filePattern),
+ : SectionCommand(InputSectionKind), filePat(filePattern),
withFlags(withFlags), withoutFlags(withoutFlags) {}
- static bool classof(const BaseCommand *c) {
+ static bool classof(const SectionCommand *c) {
return c->kind == InputSectionKind;
}
// Input sections that matches at least one of SectionPatterns
// will be associated with this InputSectionDescription.
- std::vector<SectionPattern> sectionPatterns;
+ SmallVector<SectionPattern, 0> sectionPatterns;
// Includes InputSections and MergeInputSections. Used temporarily during
// assignment of input sections to output sections.
- std::vector<InputSectionBase *> sectionBases;
+ SmallVector<InputSectionBase *, 0> sectionBases;
// Used after the finalizeInputSections() pass. MergeInputSections have been
// merged into MergeSyntheticSections.
- std::vector<InputSection *> sections;
+ SmallVector<InputSection *, 0> sections;
// Temporary record of synthetic ThunkSection instances and the pass that
// they were created in. This is used to insert newly created ThunkSections
// into Sections at the end of a createThunks() pass.
- std::vector<std::pair<ThunkSection *, uint32_t>> thunkSections;
+ SmallVector<std::pair<ThunkSection *, uint32_t>, 0> thunkSections;
// SectionPatterns can be filtered with the INPUT_SECTION_FLAGS command.
uint64_t withFlags;
};
// Represents BYTE(), SHORT(), LONG(), or QUAD().
-struct ByteCommand : BaseCommand {
+struct ByteCommand : SectionCommand {
ByteCommand(Expr e, unsigned size, std::string commandString)
- : BaseCommand(ByteKind), commandString(commandString), expression(e),
+ : SectionCommand(ByteKind), commandString(commandString), expression(e),
size(size) {}
- static bool classof(const BaseCommand *c) { return c->kind == ByteKind; }
+ static bool classof(const SectionCommand *c) { return c->kind == ByteKind; }
// Keeps string representing the command. Used for -Map" is perhaps better.
std::string commandString;
};
struct InsertCommand {
- std::vector<StringRef> names;
+ SmallVector<StringRef, 0> names;
bool isAfter;
StringRef where;
};
unsigned type = llvm::ELF::PT_NULL;
bool hasFilehdr = false;
bool hasPhdrs = false;
- llvm::Optional<unsigned> flags;
+ std::optional<unsigned> flags;
Expr lmaExpr = nullptr;
};
uint64_t tbssAddr = 0;
};
- llvm::DenseMap<StringRef, OutputSection *> nameToOutputSection;
+ llvm::DenseMap<llvm::CachedHashStringRef, OutputDesc *> nameToOutputSection;
void addSymbol(SymbolAssignment *cmd);
void assignSymbol(SymbolAssignment *cmd, bool inSec);
void expandOutputSection(uint64_t size);
void expandMemoryRegions(uint64_t size);
- std::vector<InputSectionBase *>
+ SmallVector<InputSectionBase *, 0>
computeInputSections(const InputSectionDescription *,
ArrayRef<InputSectionBase *>);
- std::vector<InputSectionBase *> createInputSectionList(OutputSection &cmd);
+ SmallVector<InputSectionBase *, 0> createInputSectionList(OutputSection &cmd);
void discardSynthetic(OutputSection &);
- std::vector<size_t> getPhdrIndices(OutputSection *sec);
-
- MemoryRegion *findMemoryRegion(OutputSection *sec);
+ SmallVector<size_t, 0> getPhdrIndices(OutputSection *sec);
- void switchTo(OutputSection *sec);
- uint64_t advance(uint64_t size, unsigned align);
- void output(InputSection *sec);
+ std::pair<MemoryRegion *, MemoryRegion *>
+ findMemoryRegion(OutputSection *sec, MemoryRegion *hint);
void assignOffsets(OutputSection *sec);
- // Ctx captures the local AddressState and makes it accessible
+ // This captures the local AddressState and makes it accessible
// deliberately. This is needed as there are some cases where we cannot just
// thread the current state through to a lambda function created by the
// script parser.
// This should remain a plain pointer as its lifetime is smaller than
// LinkerScript.
- AddressState *ctx = nullptr;
+ AddressState *state = nullptr;
OutputSection *aether;
uint64_t dot;
public:
- OutputSection *createOutputSection(StringRef name, StringRef location);
- OutputSection *getOrCreateOutputSection(StringRef name);
+ OutputDesc *createOutputSection(StringRef name, StringRef location);
+ OutputDesc *getOrCreateOutputSection(StringRef name);
bool hasPhdrsCommands() { return !phdrsCommands.empty(); }
uint64_t getDot() { return dot; }
- void discard(InputSectionBase *s);
+ void discard(InputSectionBase &s);
ExprValue getSymbolValue(StringRef name, const Twine &loc);
void addOrphanSections();
void diagnoseOrphanHandling() const;
- void adjustSectionsBeforeSorting();
+ void adjustOutputSections();
void adjustSectionsAfterSorting();
- std::vector<PhdrEntry *> createPhdrs();
+ SmallVector<PhdrEntry *, 0> createPhdrs();
bool needsInterpSection();
bool shouldKeep(InputSectionBase *s);
const Defined *assignAddresses();
- void allocateHeaders(std::vector<PhdrEntry *> &phdrs);
+ void allocateHeaders(SmallVector<PhdrEntry *, 0> &phdrs);
void processSectionCommands();
void processSymbolAssignments();
void declareSymbols();
+ bool isDiscarded(const OutputSection *sec) const;
+
// Used to handle INSERT AFTER statements.
void processInsertCommands();
// SECTIONS command list.
- std::vector<BaseCommand *> sectionCommands;
+ SmallVector<SectionCommand *, 0> sectionCommands;
// PHDRS command list.
- std::vector<PhdrsCommand> phdrsCommands;
+ SmallVector<PhdrsCommand, 0> phdrsCommands;
bool hasSectionsCommand = false;
bool errorOnMissingSection = false;
// List of section patterns specified with KEEP commands. They will
// be kept even if they are unused and --gc-sections is specified.
- std::vector<InputSectionDescription *> keptSections;
+ SmallVector<InputSectionDescription *, 0> keptSections;
// A map from memory region name to a memory region descriptor.
llvm::MapVector<llvm::StringRef, MemoryRegion *> memoryRegions;
// A list of symbols referenced by the script.
- std::vector<llvm::StringRef> referencedSymbols;
+ SmallVector<llvm::StringRef, 0> referencedSymbols;
// Used to implement INSERT [AFTER|BEFORE]. Contains output sections that need
// to be reordered.
- std::vector<InsertCommand> insertCommands;
+ SmallVector<InsertCommand, 0> insertCommands;
// OutputSections specified by OVERWRITE_SECTIONS.
- std::vector<OutputSection *> overwriteSections;
+ SmallVector<OutputDesc *, 0> overwriteSections;
// Sections that will be warned/errored by --orphan-handling.
- std::vector<const InputSectionBase *> orphanSections;
+ SmallVector<const InputSectionBase *, 0> orphanSections;
};
-extern LinkerScript *script;
+LLVM_LIBRARY_VISIBILITY extern std::unique_ptr<LinkerScript> script;
-} // end namespace elf
-} // end namespace lld
+} // end namespace lld::elf
#endif // LLD_ELF_LINKER_SCRIPT_H
#include "InputFiles.h"
#include "LinkerScript.h"
#include "OutputSections.h"
-#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
-#include "lld/Common/Strings.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"
using namespace lld;
using namespace lld::elf;
-using SymbolMapTy = DenseMap<const SectionBase *, SmallVector<Defined *, 4>>;
+using SymbolMapTy = DenseMap<const SectionBase *,
+ SmallVector<std::pair<Defined *, uint64_t>, 0>>;
static constexpr char indent8[] = " "; // 8 spaces
static constexpr char indent16[] = " "; // 16 spaces
// Returns a list of all symbols that we want to print out.
static std::vector<Defined *> getSymbols() {
std::vector<Defined *> v;
- for (InputFile *file : objectFiles)
+ for (ELFFileBase *file : ctx.objectFiles)
for (Symbol *b : file->getSymbols())
if (auto *dr = dyn_cast<Defined>(b))
if (!dr->isSection() && dr->section && dr->section->isLive() &&
- (dr->file == file || dr->needsPltAddr || dr->section->bss))
+ (dr->file == file || dr->hasFlag(NEEDS_COPY) || dr->section->bss))
v.push_back(dr);
return v;
}
static SymbolMapTy getSectionSyms(ArrayRef<Defined *> syms) {
SymbolMapTy ret;
for (Defined *dr : syms)
- ret[dr->section].push_back(dr);
+ ret[dr->section].emplace_back(dr, dr->getVA());
// Sort symbols by address. We want to print out symbols in the
// order in the output file rather than the order they appeared
// in the input files.
- for (auto &it : ret)
- llvm::stable_sort(it.second, [](Defined *a, Defined *b) {
- return a->getVA() < b->getVA();
+ SmallPtrSet<Defined *, 4> set;
+ for (auto &it : ret) {
+ // Deduplicate symbols which need a canonical PLT entry/copy relocation.
+ set.clear();
+ llvm::erase_if(it.second, [&](std::pair<Defined *, uint64_t> a) {
+ return !set.insert(a.first).second;
});
+
+ llvm::stable_sort(it.second, llvm::less_second());
+ }
return ret;
}
// we do that in batch using parallel-for.
static DenseMap<Symbol *, std::string>
getSymbolStrings(ArrayRef<Defined *> syms) {
- std::vector<std::string> str(syms.size());
- parallelForEachN(0, syms.size(), [&](size_t i) {
- raw_string_ostream os(str[i]);
+ auto strs = std::make_unique<std::string[]>(syms.size());
+ parallelFor(0, syms.size(), [&](size_t i) {
+ raw_string_ostream os(strs[i]);
OutputSection *osec = syms[i]->getOutputSection();
uint64_t vma = syms[i]->getVA();
uint64_t lma = osec ? osec->getLMA() + vma - osec->getVA(0) : 0;
DenseMap<Symbol *, std::string> ret;
for (size_t i = 0, e = syms.size(); i < e; ++i)
- ret[syms[i]] = std::move(str[i]);
+ ret[syms[i]] = std::move(strs[i]);
return ret;
}
}
}
-void elf::writeMapFile() {
- if (config->mapFile.empty())
- return;
-
- llvm::TimeTraceScope timeScope("Write map file");
-
- // Open a map file for writing.
- std::error_code ec;
- raw_fd_ostream os(config->mapFile, ec, sys::fs::OF_None);
- if (ec) {
- error("cannot open " + config->mapFile + ": " + ec.message());
- return;
- }
-
+static void writeMapFile(raw_fd_ostream &os) {
// Collect symbol info that we want to print out.
std::vector<Defined *> syms = getSymbols();
SymbolMapTy sectionSyms = getSectionSyms(syms);
<< " Size Align Out In Symbol\n";
OutputSection* osec = nullptr;
- for (BaseCommand *base : script->sectionCommands) {
- if (auto *cmd = dyn_cast<SymbolAssignment>(base)) {
- if (cmd->provide && !cmd->sym)
+ for (SectionCommand *cmd : script->sectionCommands) {
+ if (auto *assign = dyn_cast<SymbolAssignment>(cmd)) {
+ if (assign->provide && !assign->sym)
continue;
- uint64_t lma = osec ? osec->getLMA() + cmd->addr - osec->getVA(0) : 0;
- writeHeader(os, cmd->addr, lma, cmd->size, 1);
- os << cmd->commandString << '\n';
+ uint64_t lma = osec ? osec->getLMA() + assign->addr - osec->getVA(0) : 0;
+ writeHeader(os, assign->addr, lma, assign->size, 1);
+ os << assign->commandString << '\n';
continue;
}
- osec = cast<OutputSection>(base);
- writeHeader(os, osec->addr, osec->getLMA(), osec->size, osec->alignment);
+ osec = &cast<OutputDesc>(cmd)->osec;
+ writeHeader(os, osec->addr, osec->getLMA(), osec->size, osec->addralign);
os << osec->name << '\n';
// Dump symbols for each input section.
- for (BaseCommand *base : osec->sectionCommands) {
- if (auto *isd = dyn_cast<InputSectionDescription>(base)) {
+ for (SectionCommand *subCmd : osec->commands) {
+ if (auto *isd = dyn_cast<InputSectionDescription>(subCmd)) {
for (InputSection *isec : isd->sections) {
if (auto *ehSec = dyn_cast<EhFrameSection>(isec)) {
printEhFrame(os, ehSec);
continue;
}
- writeHeader(os, isec->getVA(0), osec->getLMA() + isec->getOffset(0),
- isec->getSize(), isec->alignment);
+ writeHeader(os, isec->getVA(), osec->getLMA() + isec->outSecOff,
+ isec->getSize(), isec->addralign);
os << indent8 << toString(isec) << '\n';
- for (Symbol *sym : sectionSyms[isec])
+ for (Symbol *sym : llvm::make_first_range(sectionSyms[isec]))
os << symStr[sym] << '\n';
}
continue;
}
- if (auto *cmd = dyn_cast<ByteCommand>(base)) {
- writeHeader(os, osec->addr + cmd->offset, osec->getLMA() + cmd->offset,
- cmd->size, 1);
- os << indent8 << cmd->commandString << '\n';
+ if (auto *data = dyn_cast<ByteCommand>(subCmd)) {
+ writeHeader(os, osec->addr + data->offset,
+ osec->getLMA() + data->offset, data->size, 1);
+ os << indent8 << data->commandString << '\n';
continue;
}
- if (auto *cmd = dyn_cast<SymbolAssignment>(base)) {
- if (cmd->provide && !cmd->sym)
+ if (auto *assign = dyn_cast<SymbolAssignment>(subCmd)) {
+ if (assign->provide && !assign->sym)
continue;
- writeHeader(os, cmd->addr, osec->getLMA() + cmd->addr - osec->getVA(0),
- cmd->size, 1);
- os << indent8 << cmd->commandString << '\n';
+ writeHeader(os, assign->addr,
+ osec->getLMA() + assign->addr - osec->getVA(0),
+ assign->size, 1);
+ os << indent8 << assign->commandString << '\n';
continue;
}
}
}
}
-static void print(StringRef a, StringRef b) {
- lld::outs() << left_justify(a, 49) << " " << b << "\n";
-}
-
// Output a cross reference table to stdout. This is for --cref.
//
// For each global symbol, we print out a file that defines the symbol
//
// In this case, strlen is defined by libc.so.6 and used by other two
// files.
-void elf::writeCrossReferenceTable() {
- if (!config->cref)
- return;
-
+static void writeCref(raw_fd_ostream &os) {
// Collect symbols and files.
MapVector<Symbol *, SetVector<InputFile *>> map;
- for (InputFile *file : objectFiles) {
+ for (ELFFileBase *file : ctx.objectFiles) {
for (Symbol *sym : file->getSymbols()) {
if (isa<SharedSymbol>(sym))
map[sym].insert(file);
}
}
- // Print out a header.
- lld::outs() << "Cross Reference Table\n\n";
+ auto print = [&](StringRef a, StringRef b) {
+ os << left_justify(a, 49) << ' ' << b << '\n';
+ };
+
+ // Print a blank line and a header. The format matches GNU ld.
+ os << "\nCross Reference Table\n\n";
print("Symbol", "File");
// Print out a table.
}
}
-void elf::writeArchiveStats() {
- if (config->printArchiveStats.empty())
+void elf::writeMapAndCref() {
+ if (config->mapFile.empty() && !config->cref)
return;
+ llvm::TimeTraceScope timeScope("Write map file");
+
+ // Open a map file for writing.
std::error_code ec;
- raw_fd_ostream os(config->printArchiveStats, ec, sys::fs::OF_None);
+ StringRef mapFile = config->mapFile.empty() ? "-" : config->mapFile;
+ raw_fd_ostream os(mapFile, ec, sys::fs::OF_None);
if (ec) {
- error("--print-archive-stats=: cannot open " + config->printArchiveStats +
- ": " + ec.message());
+ error("cannot open " + mapFile + ": " + ec.message());
return;
}
- os << "members\tfetched\tarchive\n";
- for (const ArchiveFile *f : archiveFiles)
- os << f->getMemberCount() << '\t' << f->getFetchedMemberCount() << '\t'
- << f->getName() << '\n';
+ if (!config->mapFile.empty())
+ writeMapFile(os);
+ if (config->cref)
+ writeCref(os);
}
#ifndef LLD_ELF_MAPFILE_H
#define LLD_ELF_MAPFILE_H
-namespace lld {
-namespace elf {
-void writeMapFile();
-void writeCrossReferenceTable();
-void writeArchiveStats();
-} // namespace elf
-} // namespace lld
+namespace lld::elf {
+void writeMapAndCref();
+}
#endif
//===----------------------------------------------------------------------===//
#include "MarkLive.h"
+#include "InputFiles.h"
#include "InputSection.h"
#include "LinkerScript.h"
-#include "OutputSections.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Strings.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Object/ELF.h"
#include "llvm/Support/TimeProfiler.h"
-#include <functional>
#include <vector>
using namespace llvm;
SmallVector<InputSection *, 0> queue;
// There are normally few input sections whose names are valid C
- // identifiers, so we just store a std::vector instead of a multimap.
- DenseMap<StringRef, std::vector<InputSectionBase *>> cNamedSections;
+ // identifiers, so we just store a SmallVector instead of a multimap.
+ DenseMap<StringRef, SmallVector<InputSectionBase *, 0>> cNamedSections;
};
} // namespace
template <class ELFT>
static uint64_t getAddend(InputSectionBase &sec,
const typename ELFT::Rel &rel) {
- return target->getImplicitAddend(sec.data().begin() + rel.r_offset,
+ return target->getImplicitAddend(sec.content().begin() + rel.r_offset,
rel.getType(config->isMips64EL));
}
if (auto *ss = dyn_cast<SharedSymbol>(&sym))
if (!ss->isWeak())
- ss->getFile().isNeeded = true;
+ cast<SharedFile>(ss->file)->isNeeded = true;
for (InputSectionBase *sec : cNamedSections.lookup(sym.getName()))
enqueue(sec, 0);
template <class RelTy>
void MarkLive<ELFT>::scanEhFrameSection(EhInputSection &eh,
ArrayRef<RelTy> rels) {
- for (size_t i = 0, end = eh.pieces.size(); i < end; ++i) {
- EhSectionPiece &piece = eh.pieces[i];
- size_t firstRelI = piece.firstRelocation;
+ for (const EhSectionPiece &cie : eh.cies)
+ if (cie.firstRelocation != unsigned(-1))
+ resolveReloc(eh, rels[cie.firstRelocation], false);
+ for (const EhSectionPiece &fde : eh.fdes) {
+ size_t firstRelI = fde.firstRelocation;
if (firstRelI == (unsigned)-1)
continue;
-
- if (read32<ELFT::TargetEndianness>(piece.data().data() + 4) == 0) {
- // This is a CIE, we only need to worry about the first relocation. It is
- // known to point to the personality function.
- resolveReloc(eh, rels[firstRelI], false);
- continue;
- }
-
- uint64_t pieceEnd = piece.inputOff + piece.size;
+ uint64_t pieceEnd = fde.inputOff + fde.size;
for (size_t j = firstRelI, end2 = rels.size();
j < end2 && rels[j].r_offset < pieceEnd; ++j)
resolveReloc(eh, rels[j], true);
// SHT_NOTE sections in a group are subject to garbage collection.
return !sec->nextInSectionGroup;
default:
+ // Support SHT_PROGBITS .init_array (https://golang.org/issue/50295) and
+ // .init_array.N (https://github.com/rust-lang/rust/issues/92181) for a
+ // while.
StringRef s = sec->name;
- return s.startswith(".ctors") || s.startswith(".dtors") ||
- s.startswith(".init") || s.startswith(".fini") ||
- s.startswith(".jcr");
+ return s == ".init" || s == ".fini" || s.startswith(".init_array") ||
+ s == ".jcr" || s.startswith(".ctors") || s.startswith(".dtors");
}
}
template <class ELFT>
void MarkLive<ELFT>::enqueue(InputSectionBase *sec, uint64_t offset) {
- // Skip over discarded sections. This in theory shouldn't happen, because
- // the ELF spec doesn't allow a relocation to point to a deduplicated
- // COMDAT section directly. Unfortunately this happens in practice (e.g.
- // .eh_frame) so we need to add a check.
- if (sec == &InputSection::discarded)
- return;
-
// Usually, a whole section is marked as live or dead, but in mergeable
// (splittable) sections, each piece of data has independent liveness bit.
// So we explicitly tell it which offset is in use.
if (auto *ms = dyn_cast<MergeInputSection>(sec))
- ms->getSectionPiece(offset)->live = true;
+ ms->getSectionPiece(offset).live = true;
// Set Sec->Partition to the meet (i.e. the "minimum") of Partition and
// Sec->Partition in the following lattice: 1 < other < 0. If Sec->Partition
// Preserve externally-visible symbols if the symbols defined by this
// file can interrupt other ELF file's symbols at runtime.
- for (Symbol *sym : symtab->symbols())
+ for (Symbol *sym : symtab.getSymbols())
if (sym->includeInDynsym() && sym->partition == partition)
markSymbol(sym);
return;
}
- markSymbol(symtab->find(config->entry));
- markSymbol(symtab->find(config->init));
- markSymbol(symtab->find(config->fini));
+ markSymbol(symtab.find(config->entry));
+ markSymbol(symtab.find(config->init));
+ markSymbol(symtab.find(config->fini));
for (StringRef s : config->undefined)
- markSymbol(symtab->find(s));
+ markSymbol(symtab.find(s));
for (StringRef s : script->referencedSymbols)
- markSymbol(symtab->find(s));
-
- // Preserve special sections and those which are specified in linker
- // script KEEP command.
- for (InputSectionBase *sec : inputSections) {
- // Mark .eh_frame sections as live because there are usually no relocations
- // that point to .eh_frames. Otherwise, the garbage collector would drop
- // all of them. We also want to preserve personality routines and LSDA
- // referenced by .eh_frame sections, so we scan them for that here.
- if (auto *eh = dyn_cast<EhInputSection>(sec)) {
- eh->markLive();
- if (!eh->numRelocations)
- continue;
-
- if (eh->areRelocsRela)
- scanEhFrameSection(*eh, eh->template relas<ELFT>());
- else
- scanEhFrameSection(*eh, eh->template rels<ELFT>());
- }
-
+ markSymbol(symtab.find(s));
+
+ // Mark .eh_frame sections as live because there are usually no relocations
+ // that point to .eh_frames. Otherwise, the garbage collector would drop
+ // all of them. We also want to preserve personality routines and LSDA
+ // referenced by .eh_frame sections, so we scan them for that here.
+ for (EhInputSection *eh : ctx.ehInputSections) {
+ const RelsOrRelas<ELFT> rels = eh->template relsOrRelas<ELFT>();
+ if (rels.areRelocsRel())
+ scanEhFrameSection(*eh, rels.rels);
+ else if (rels.relas.size())
+ scanEhFrameSection(*eh, rels.relas);
+ }
+ for (InputSectionBase *sec : ctx.inputSections) {
if (sec->flags & SHF_GNU_RETAIN) {
enqueue(sec, 0);
continue;
if (sec->flags & SHF_LINK_ORDER)
continue;
+ // Usually, non-SHF_ALLOC sections are not removed even if they are
+ // unreachable through relocations because reachability is not a good signal
+ // whether they are garbage or not (e.g. there is usually no section
+ // referring to a .comment section, but we want to keep it.) When a
+ // non-SHF_ALLOC section is retained, we also retain sections dependent on
+ // it.
+ //
+ // Note on SHF_LINK_ORDER: Such sections contain metadata and they
+ // have a reverse dependency on the InputSection they are linked with.
+ // We are able to garbage collect them.
+ //
+ // Note on SHF_REL{,A}: Such sections reach here only when -r
+ // or --emit-reloc were given. And they are subject of garbage
+ // collection because, if we remove a text section, we also
+ // remove its relocation section.
+ //
+ // Note on nextInSectionGroup: The ELF spec says that group sections are
+ // included or omitted as a unit. We take the interpretation that:
+ //
+ // - Group members (nextInSectionGroup != nullptr) are subject to garbage
+ // collection.
+ // - Groups members are retained or discarded as a unit.
+ if (!(sec->flags & SHF_ALLOC)) {
+ bool isRel = sec->type == SHT_REL || sec->type == SHT_RELA;
+ if (!isRel && !sec->nextInSectionGroup) {
+ sec->markLive();
+ for (InputSection *isec : sec->dependentSections)
+ isec->markLive();
+ }
+ }
+
+ // Preserve special sections and those which are specified in linker
+ // script KEEP command.
if (isReserved(sec) || script->shouldKeep(sec)) {
enqueue(sec, 0);
} else if ((!config->zStartStopGC || sec->name.startswith("__libc_")) &&
// As a workaround for glibc libc.a before 2.34
// (https://sourceware.org/PR27492), retain __libc_atexit and similar
// sections regardless of zStartStopGC.
- cNamedSections[saver.save("__start_" + sec->name)].push_back(sec);
- cNamedSections[saver.save("__stop_" + sec->name)].push_back(sec);
+ cNamedSections[saver().save("__start_" + sec->name)].push_back(sec);
+ cNamedSections[saver().save("__stop_" + sec->name)].push_back(sec);
}
}
while (!queue.empty()) {
InputSectionBase &sec = *queue.pop_back_val();
- if (sec.areRelocsRela) {
- for (const typename ELFT::Rela &rel : sec.template relas<ELFT>())
- resolveReloc(sec, rel, false);
- } else {
- for (const typename ELFT::Rel &rel : sec.template rels<ELFT>())
- resolveReloc(sec, rel, false);
- }
+ const RelsOrRelas<ELFT> rels = sec.template relsOrRelas<ELFT>();
+ for (const typename ELFT::Rel &rel : rels.rels)
+ resolveReloc(sec, rel, false);
+ for (const typename ELFT::Rela &rel : rels.relas)
+ resolveReloc(sec, rel, false);
for (InputSectionBase *isec : sec.dependentSections)
enqueue(isec, 0);
// to from __start_/__stop_ symbols because there will only be one set of
// symbols for the whole program.
template <class ELFT> void MarkLive<ELFT>::moveToMain() {
- for (InputFile *file : objectFiles)
+ for (ELFFileBase *file : ctx.objectFiles)
for (Symbol *s : file->getSymbols())
if (auto *d = dyn_cast<Defined>(s))
if ((d->type == STT_GNU_IFUNC || d->type == STT_TLS) && d->section &&
d->section->isLive())
markSymbol(s);
- for (InputSectionBase *sec : inputSections) {
+ for (InputSectionBase *sec : ctx.inputSections) {
if (!sec->isLive() || !isValidCIdentifier(sec->name))
continue;
- if (symtab->find(("__start_" + sec->name).str()) ||
- symtab->find(("__stop_" + sec->name).str()))
+ if (symtab.find(("__start_" + sec->name).str()) ||
+ symtab.find(("__stop_" + sec->name).str()))
enqueue(sec, 0);
}
// so that they are emitted to the output file.
template <class ELFT> void elf::markLive() {
llvm::TimeTraceScope timeScope("markLive");
- // If -gc-sections is not given, no sections are removed.
+ // If --gc-sections is not given, retain all input sections.
if (!config->gcSections) {
- for (InputSectionBase *sec : inputSections)
- sec->markLive();
-
// If a DSO defines a symbol referenced in a regular object, it is needed.
- for (Symbol *sym : symtab->symbols())
+ for (Symbol *sym : symtab.getSymbols())
if (auto *s = dyn_cast<SharedSymbol>(sym))
if (s->isUsedInRegularObj && !s->isWeak())
- s->getFile().isNeeded = true;
+ cast<SharedFile>(s->file)->isNeeded = true;
return;
}
- // Otherwise, do mark-sweep GC.
- //
- // The -gc-sections option works only for SHF_ALLOC sections (sections that
- // are memory-mapped at runtime). So we can unconditionally make non-SHF_ALLOC
- // sections alive except SHF_LINK_ORDER, SHT_REL/SHT_RELA sections, and
- // sections in a group.
- //
- // Usually, non-SHF_ALLOC sections are not removed even if they are
- // unreachable through relocations because reachability is not a good signal
- // whether they are garbage or not (e.g. there is usually no section referring
- // to a .comment section, but we want to keep it.) When a non-SHF_ALLOC
- // section is retained, we also retain sections dependent on it.
- //
- // Note on SHF_LINK_ORDER: Such sections contain metadata and they
- // have a reverse dependency on the InputSection they are linked with.
- // We are able to garbage collect them.
- //
- // Note on SHF_REL{,A}: Such sections reach here only when -r
- // or -emit-reloc were given. And they are subject of garbage
- // collection because, if we remove a text section, we also
- // remove its relocation section.
- //
- // Note on nextInSectionGroup: The ELF spec says that group sections are
- // included or omitted as a unit. We take the interpretation that:
- //
- // - Group members (nextInSectionGroup != nullptr) are subject to garbage
- // collection.
- // - Groups members are retained or discarded as a unit.
- for (InputSectionBase *sec : inputSections) {
- bool isAlloc = (sec->flags & SHF_ALLOC);
- bool isLinkOrder = (sec->flags & SHF_LINK_ORDER);
- bool isRel = (sec->type == SHT_REL || sec->type == SHT_RELA);
-
- if (!isAlloc && !isLinkOrder && !isRel && !sec->nextInSectionGroup) {
- sec->markLive();
- for (InputSection *isec : sec->dependentSections)
- isec->markLive();
- }
- }
+ for (InputSectionBase *sec : ctx.inputSections)
+ sec->markDead();
// Follow the graph to mark all live sections.
for (unsigned curPart = 1; curPart <= partitions.size(); ++curPart)
// Report garbage-collected sections.
if (config->printGcSections)
- for (InputSectionBase *sec : inputSections)
+ for (InputSectionBase *sec : ctx.inputSections)
if (!sec->isLive())
message("removing unused section " + toString(sec));
}
#ifndef LLD_ELF_MARKLIVE_H
#define LLD_ELF_MARKLIVE_H
-namespace lld {
-namespace elf {
+namespace lld::elf {
template <class ELFT> void markLive();
-} // namespace elf
-} // namespace lld
+}
#endif // LLD_ELF_MARKLIVE_H
#include "OutputSections.h"
#include "Config.h"
+#include "InputFiles.h"
#include "LinkerScript.h"
-#include "SymbolTable.h"
+#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
+#include "lld/Common/Arrays.h"
#include "lld/Common/Memory.h"
-#include "lld/Common/Strings.h"
#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/Config/llvm-config.h" // LLVM_ENABLE_ZLIB
#include "llvm/Support/Compression.h"
-#include "llvm/Support/MD5.h"
-#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Parallel.h"
-#include "llvm/Support/SHA1.h"
+#include "llvm/Support/Path.h"
#include "llvm/Support/TimeProfiler.h"
-#include <regex>
-#include <unordered_set>
+#if LLVM_ENABLE_ZLIB
+#include <zlib.h>
+#endif
+#if LLVM_ENABLE_ZSTD
+#include <zstd.h>
+#endif
using namespace llvm;
using namespace llvm::dwarf;
using namespace lld::elf;
uint8_t *Out::bufferStart;
-uint8_t Out::first;
PhdrEntry *Out::tlsPhdr;
OutputSection *Out::elfHeader;
OutputSection *Out::programHeaders;
OutputSection *Out::initArray;
OutputSection *Out::finiArray;
-std::vector<OutputSection *> elf::outputSections;
+SmallVector<OutputSection *, 0> elf::outputSections;
uint32_t OutputSection::getPhdrFlags() const {
uint32_t ret = 0;
template <class ELFT>
void OutputSection::writeHeaderTo(typename ELFT::Shdr *shdr) {
shdr->sh_entsize = entsize;
- shdr->sh_addralign = alignment;
+ shdr->sh_addralign = addralign;
shdr->sh_type = type;
shdr->sh_offset = offset;
shdr->sh_flags = flags;
}
OutputSection::OutputSection(StringRef name, uint32_t type, uint64_t flags)
- : BaseCommand(OutputSectionKind),
- SectionBase(Output, name, flags, /*Entsize*/ 0, /*Alignment*/ 1, type,
+ : SectionBase(Output, name, flags, /*Entsize*/ 0, /*Alignment*/ 1, type,
/*Info*/ 0, /*Link*/ 0) {}
// We allow sections of types listed below to merged into a
void OutputSection::recordSection(InputSectionBase *isec) {
partition = isec->partition;
isec->parent = this;
- if (sectionCommands.empty() ||
- !isa<InputSectionDescription>(sectionCommands.back()))
- sectionCommands.push_back(make<InputSectionDescription>(""));
- auto *isd = cast<InputSectionDescription>(sectionCommands.back());
+ if (commands.empty() || !isa<InputSectionDescription>(commands.back()))
+ commands.push_back(make<InputSectionDescription>(""));
+ auto *isd = cast<InputSectionDescription>(commands.back());
isd->sectionBases.push_back(isec);
}
// isec. Also check whether the InputSection flags and type are consistent with
// other InputSections.
void OutputSection::commitSection(InputSection *isec) {
+ if (LLVM_UNLIKELY(type != isec->type)) {
+ if (hasInputSections || typeIsSet) {
+ if (typeIsSet || !canMergeToProgbits(type) ||
+ !canMergeToProgbits(isec->type)) {
+ // Changing the type of a (NOLOAD) section is fishy, but some projects
+ // (e.g. https://github.com/ClangBuiltLinux/linux/issues/1597)
+ // traditionally rely on the behavior. Issue a warning to not break
+ // them. Other types get an error.
+ auto diagnose = type == SHT_NOBITS ? warn : errorOrWarn;
+ diagnose("section type mismatch for " + isec->name + "\n>>> " +
+ toString(isec) + ": " +
+ getELFSectionTypeName(config->emachine, isec->type) +
+ "\n>>> output section " + name + ": " +
+ getELFSectionTypeName(config->emachine, type));
+ }
+ if (!typeIsSet)
+ type = SHT_PROGBITS;
+ } else {
+ type = isec->type;
+ }
+ }
if (!hasInputSections) {
// If IS is the first section to be added to this section,
// initialize type, entsize and flags from isec.
hasInputSections = true;
- type = isec->type;
entsize = isec->entsize;
flags = isec->flags;
} else {
// Otherwise, check if new type or flags are compatible with existing ones.
if ((flags ^ isec->flags) & SHF_TLS)
- error("incompatible section flags for " + name + "\n>>> " + toString(isec) +
- ": 0x" + utohexstr(isec->flags) + "\n>>> output section " + name +
- ": 0x" + utohexstr(flags));
-
- if (type != isec->type) {
- if (!canMergeToProgbits(type) || !canMergeToProgbits(isec->type))
- error("section type mismatch for " + isec->name + "\n>>> " +
- toString(isec) + ": " +
- getELFSectionTypeName(config->emachine, isec->type) +
- "\n>>> output section " + name + ": " +
- getELFSectionTypeName(config->emachine, type));
- type = SHT_PROGBITS;
- }
+ error("incompatible section flags for " + name + "\n>>> " +
+ toString(isec) + ": 0x" + utohexstr(isec->flags) +
+ "\n>>> output section " + name + ": 0x" + utohexstr(flags));
}
- if (noload)
- type = SHT_NOBITS;
isec->parent = this;
uint64_t andMask =
if (nonAlloc)
flags &= ~(uint64_t)SHF_ALLOC;
- alignment = std::max(alignment, isec->alignment);
+ addralign = std::max(addralign, isec->addralign);
// If this section contains a table of fixed-size entries, sh_entsize
// holds the element size. If it contains elements of different size we
entsize = 0;
}
+static MergeSyntheticSection *createMergeSynthetic(StringRef name,
+ uint32_t type,
+ uint64_t flags,
+ uint32_t addralign) {
+ if ((flags & SHF_STRINGS) && config->optimize >= 2)
+ return make<MergeTailSection>(name, type, flags, addralign);
+ return make<MergeNoTailSection>(name, type, flags, addralign);
+}
+
// This function scans over the InputSectionBase list sectionBases to create
// InputSectionDescription::sections.
//
// to compute an output offset for each piece of each input section.
void OutputSection::finalizeInputSections() {
std::vector<MergeSyntheticSection *> mergeSections;
- for (BaseCommand *base : sectionCommands) {
- auto *cmd = dyn_cast<InputSectionDescription>(base);
- if (!cmd)
+ for (SectionCommand *cmd : commands) {
+ auto *isd = dyn_cast<InputSectionDescription>(cmd);
+ if (!isd)
continue;
- cmd->sections.reserve(cmd->sectionBases.size());
- for (InputSectionBase *s : cmd->sectionBases) {
+ isd->sections.reserve(isd->sectionBases.size());
+ for (InputSectionBase *s : isd->sectionBases) {
MergeInputSection *ms = dyn_cast<MergeInputSection>(s);
if (!ms) {
- cmd->sections.push_back(cast<InputSection>(s));
+ isd->sections.push_back(cast<InputSection>(s));
continue;
}
//
// SHF_STRINGS section with different alignments should not be merged.
return sec->flags == ms->flags && sec->entsize == ms->entsize &&
- (sec->alignment == ms->alignment || !(sec->flags & SHF_STRINGS));
+ (sec->addralign == ms->addralign || !(sec->flags & SHF_STRINGS));
});
if (i == mergeSections.end()) {
MergeSyntheticSection *syn =
- createMergeSynthetic(name, ms->type, ms->flags, ms->alignment);
+ createMergeSynthetic(name, ms->type, ms->flags, ms->addralign);
mergeSections.push_back(syn);
i = std::prev(mergeSections.end());
syn->entsize = ms->entsize;
- cmd->sections.push_back(syn);
+ isd->sections.push_back(syn);
}
(*i)->addSection(ms);
}
// sectionBases should not be used from this point onwards. Clear it to
// catch misuses.
- cmd->sectionBases.clear();
+ isd->sectionBases.clear();
// Some input sections may be removed from the list after ICF.
- for (InputSection *s : cmd->sections)
+ for (InputSection *s : isd->sections)
commitSection(s);
}
for (auto *ms : mergeSections)
return Out::elfHeader->size + Out::programHeaders->size;
}
-bool OutputSection::classof(const BaseCommand *c) {
- return c->kind == OutputSectionKind;
-}
-
void OutputSection::sort(llvm::function_ref<int(InputSectionBase *s)> order) {
assert(isLive());
- for (BaseCommand *b : sectionCommands)
+ for (SectionCommand *b : commands)
if (auto *isd = dyn_cast<InputSectionDescription>(b))
sortByOrder(isd->sections, order);
}
memcpy(buf + i, filler.data(), size - i);
}
+#if LLVM_ENABLE_ZLIB
+static SmallVector<uint8_t, 0> deflateShard(ArrayRef<uint8_t> in, int level,
+ int flush) {
+ // 15 and 8 are default. windowBits=-15 is negative to generate raw deflate
+ // data with no zlib header or trailer.
+ z_stream s = {};
+ deflateInit2(&s, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
+ s.next_in = const_cast<uint8_t *>(in.data());
+ s.avail_in = in.size();
+
+ // Allocate a buffer of half of the input size, and grow it by 1.5x if
+ // insufficient.
+ SmallVector<uint8_t, 0> out;
+ size_t pos = 0;
+ out.resize_for_overwrite(std::max<size_t>(in.size() / 2, 64));
+ do {
+ if (pos == out.size())
+ out.resize_for_overwrite(out.size() * 3 / 2);
+ s.next_out = out.data() + pos;
+ s.avail_out = out.size() - pos;
+ (void)deflate(&s, flush);
+ pos = s.next_out - out.data();
+ } while (s.avail_out == 0);
+ assert(s.avail_in == 0);
+
+ out.truncate(pos);
+ deflateEnd(&s);
+ return out;
+}
+#endif
+
// Compress section contents if this section contains debug info.
template <class ELFT> void OutputSection::maybeCompress() {
using Elf_Chdr = typename ELFT::Chdr;
+ (void)sizeof(Elf_Chdr);
// Compress only DWARF debug sections.
- if (!config->compressDebugSections || (flags & SHF_ALLOC) ||
- !name.startswith(".debug_"))
+ if (config->compressDebugSections == DebugCompressionType::None ||
+ (flags & SHF_ALLOC) || !name.startswith(".debug_") || size == 0)
return;
llvm::TimeTraceScope timeScope("Compress debug sections");
+ compressed.uncompressedSize = size;
+ auto buf = std::make_unique<uint8_t[]>(size);
+ // Write uncompressed data to a temporary zero-initialized buffer.
+ {
+ parallel::TaskGroup tg;
+ writeTo<ELFT>(buf.get(), tg);
+ }
- // Create a section header.
- zDebugHeader.resize(sizeof(Elf_Chdr));
- auto *hdr = reinterpret_cast<Elf_Chdr *>(zDebugHeader.data());
- hdr->ch_type = ELFCOMPRESS_ZLIB;
- hdr->ch_size = size;
- hdr->ch_addralign = alignment;
-
- // Write section contents to a temporary buffer and compress it.
- std::vector<uint8_t> buf(size);
- writeTo<ELFT>(buf.data());
- // We chose 1 as the default compression level because it is the fastest. If
- // -O2 is given, we use level 6 to compress debug info more by ~15%. We found
- // that level 7 to 9 doesn't make much difference (~1% more compression) while
- // they take significant amount of time (~2x), so level 6 seems enough.
- if (Error e = zlib::compress(toStringRef(buf), compressedData,
- config->optimize >= 2 ? 6 : 1))
- fatal("compress failed: " + llvm::toString(std::move(e)));
-
- // Update section headers.
- size = sizeof(Elf_Chdr) + compressedData.size();
+#if LLVM_ENABLE_ZSTD
+ // Use ZSTD's streaming compression API which permits parallel workers working
+ // on the stream. See http://facebook.github.io/zstd/zstd_manual.html
+ // "Streaming compression - HowTo".
+ if (config->compressDebugSections == DebugCompressionType::Zstd) {
+ // Allocate a buffer of half of the input size, and grow it by 1.5x if
+ // insufficient.
+ compressed.shards = std::make_unique<SmallVector<uint8_t, 0>[]>(1);
+ SmallVector<uint8_t, 0> &out = compressed.shards[0];
+ out.resize_for_overwrite(std::max<size_t>(size / 2, 32));
+ size_t pos = 0;
+
+ ZSTD_CCtx *cctx = ZSTD_createCCtx();
+ // Ignore error if zstd was not built with ZSTD_MULTITHREAD.
+ (void)ZSTD_CCtx_setParameter(cctx, ZSTD_c_nbWorkers,
+ parallel::strategy.compute_thread_count());
+ ZSTD_outBuffer zob = {out.data(), out.size(), 0};
+ ZSTD_EndDirective directive = ZSTD_e_continue;
+ const size_t blockSize = ZSTD_CStreamInSize();
+ do {
+ const size_t n = std::min(static_cast<size_t>(size - pos), blockSize);
+ if (n == size - pos)
+ directive = ZSTD_e_end;
+ ZSTD_inBuffer zib = {buf.get() + pos, n, 0};
+ size_t bytesRemaining = 0;
+ while (zib.pos != zib.size ||
+ (directive == ZSTD_e_end && bytesRemaining != 0)) {
+ if (zob.pos == zob.size) {
+ out.resize_for_overwrite(out.size() * 3 / 2);
+ zob.dst = out.data();
+ zob.size = out.size();
+ }
+ bytesRemaining = ZSTD_compressStream2(cctx, &zob, &zib, directive);
+ assert(!ZSTD_isError(bytesRemaining));
+ }
+ pos += n;
+ } while (directive != ZSTD_e_end);
+ out.resize(zob.pos);
+ ZSTD_freeCCtx(cctx);
+
+ size = sizeof(Elf_Chdr) + out.size();
+ flags |= SHF_COMPRESSED;
+ return;
+ }
+#endif
+
+#if LLVM_ENABLE_ZLIB
+ // We chose 1 (Z_BEST_SPEED) as the default compression level because it is
+ // the fastest. If -O2 is given, we use level 6 to compress debug info more by
+ // ~15%. We found that level 7 to 9 doesn't make much difference (~1% more
+ // compression) while they take significant amount of time (~2x), so level 6
+ // seems enough.
+ const int level = config->optimize >= 2 ? 6 : Z_BEST_SPEED;
+
+ // Split input into 1-MiB shards.
+ constexpr size_t shardSize = 1 << 20;
+ auto shardsIn = split(ArrayRef<uint8_t>(buf.get(), size), shardSize);
+ const size_t numShards = shardsIn.size();
+
+ // Compress shards and compute Alder-32 checksums. Use Z_SYNC_FLUSH for all
+ // shards but the last to flush the output to a byte boundary to be
+ // concatenated with the next shard.
+ auto shardsOut = std::make_unique<SmallVector<uint8_t, 0>[]>(numShards);
+ auto shardsAdler = std::make_unique<uint32_t[]>(numShards);
+ parallelFor(0, numShards, [&](size_t i) {
+ shardsOut[i] = deflateShard(shardsIn[i], level,
+ i != numShards - 1 ? Z_SYNC_FLUSH : Z_FINISH);
+ shardsAdler[i] = adler32(1, shardsIn[i].data(), shardsIn[i].size());
+ });
+
+ // Update section size and combine Alder-32 checksums.
+ uint32_t checksum = 1; // Initial Adler-32 value
+ size = sizeof(Elf_Chdr) + 2; // Elf_Chdir and zlib header
+ for (size_t i = 0; i != numShards; ++i) {
+ size += shardsOut[i].size();
+ checksum = adler32_combine(checksum, shardsAdler[i], shardsIn[i].size());
+ }
+ size += 4; // checksum
+
+ compressed.shards = std::move(shardsOut);
+ compressed.numShards = numShards;
+ compressed.checksum = checksum;
flags |= SHF_COMPRESSED;
+#endif
}
static void writeInt(uint8_t *buf, uint64_t data, uint64_t size) {
llvm_unreachable("unsupported Size argument");
}
-template <class ELFT> void OutputSection::writeTo(uint8_t *buf) {
+template <class ELFT>
+void OutputSection::writeTo(uint8_t *buf, parallel::TaskGroup &tg) {
+ llvm::TimeTraceScope timeScope("Write sections", name);
if (type == SHT_NOBITS)
return;
- // If -compress-debug-section is specified and if this is a debug section,
+ // If --compress-debug-section is specified and if this is a debug section,
// we've already compressed section contents. If that's the case,
// just write it down.
- if (!compressedData.empty()) {
- memcpy(buf, zDebugHeader.data(), zDebugHeader.size());
- memcpy(buf + zDebugHeader.size(), compressedData.data(),
- compressedData.size());
+ if (compressed.shards) {
+ auto *chdr = reinterpret_cast<typename ELFT::Chdr *>(buf);
+ chdr->ch_size = compressed.uncompressedSize;
+ chdr->ch_addralign = addralign;
+ buf += sizeof(*chdr);
+ if (config->compressDebugSections == DebugCompressionType::Zstd) {
+ chdr->ch_type = ELFCOMPRESS_ZSTD;
+ memcpy(buf, compressed.shards[0].data(), compressed.shards[0].size());
+ return;
+ }
+ chdr->ch_type = ELFCOMPRESS_ZLIB;
+
+ // Compute shard offsets.
+ auto offsets = std::make_unique<size_t[]>(compressed.numShards);
+ offsets[0] = 2; // zlib header
+ for (size_t i = 1; i != compressed.numShards; ++i)
+ offsets[i] = offsets[i - 1] + compressed.shards[i - 1].size();
+
+ buf[0] = 0x78; // CMF
+ buf[1] = 0x01; // FLG: best speed
+ parallelFor(0, compressed.numShards, [&](size_t i) {
+ memcpy(buf + offsets[i], compressed.shards[i].data(),
+ compressed.shards[i].size());
+ });
+
+ write32be(buf + (size - sizeof(*chdr) - 4), compressed.checksum);
return;
}
// Write leading padding.
- std::vector<InputSection *> sections = getInputSections(this);
+ ArrayRef<InputSection *> sections = getInputSections(*this, storage);
std::array<uint8_t, 4> filler = getFiller();
bool nonZeroFiller = read32(filler.data()) != 0;
if (nonZeroFiller)
fill(buf, sections.empty() ? size : sections[0]->outSecOff, filler);
- parallelForEachN(0, sections.size(), [&](size_t i) {
- InputSection *isec = sections[i];
- isec->writeTo<ELFT>(buf);
-
- // Fill gaps between sections.
- if (nonZeroFiller) {
- uint8_t *start = buf + isec->outSecOff + isec->getSize();
- uint8_t *end;
- if (i + 1 == sections.size())
- end = buf + size;
+ auto fn = [=](size_t begin, size_t end) {
+ size_t numSections = sections.size();
+ for (size_t i = begin; i != end; ++i) {
+ InputSection *isec = sections[i];
+ if (auto *s = dyn_cast<SyntheticSection>(isec))
+ s->writeTo(buf + isec->outSecOff);
else
- end = buf + sections[i + 1]->outSecOff;
- if (isec->nopFiller) {
- assert(target->nopInstrs);
- nopInstrFill(start, end - start);
- } else
- fill(start, end - start, filler);
+ isec->writeTo<ELFT>(buf + isec->outSecOff);
+
+ // Fill gaps between sections.
+ if (nonZeroFiller) {
+ uint8_t *start = buf + isec->outSecOff + isec->getSize();
+ uint8_t *end;
+ if (i + 1 == numSections)
+ end = buf + size;
+ else
+ end = buf + sections[i + 1]->outSecOff;
+ if (isec->nopFiller) {
+ assert(target->nopInstrs);
+ nopInstrFill(start, end - start);
+ } else
+ fill(start, end - start, filler);
+ }
}
- });
-
- // Linker scripts may have BYTE()-family commands with which you
- // can write arbitrary bytes to the output. Process them if any.
- for (BaseCommand *base : sectionCommands)
- if (auto *data = dyn_cast<ByteCommand>(base))
+ };
+
+ // If there is any BYTE()-family command (rare), write the section content
+ // first then process BYTE to overwrite the filler content. The write is
+ // serial due to the limitation of llvm/Support/Parallel.h.
+ bool written = false;
+ size_t numSections = sections.size();
+ for (SectionCommand *cmd : commands)
+ if (auto *data = dyn_cast<ByteCommand>(cmd)) {
+ if (!std::exchange(written, true))
+ fn(0, numSections);
writeInt(buf + data->offset, data->expression().getValue(), data->size);
-}
+ }
+ if (written || !numSections)
+ return;
-static void finalizeShtGroup(OutputSection *os,
- InputSection *section) {
- assert(config->relocatable);
+ // There is no data command. Write content asynchronously to overlap the write
+ // time with other output sections. Note, if a linker script specifies
+ // overlapping output sections (needs --noinhibit-exec or --no-check-sections
+ // to supress the error), the output may be non-deterministic.
+ const size_t taskSizeLimit = 4 << 20;
+ for (size_t begin = 0, i = 0, taskSize = 0;;) {
+ taskSize += sections[i]->getSize();
+ bool done = ++i == numSections;
+ if (done || taskSize >= taskSizeLimit) {
+ tg.execute([=] { fn(begin, i); });
+ if (done)
+ break;
+ begin = i;
+ taskSize = 0;
+ }
+ }
+}
+static void finalizeShtGroup(OutputSection *os, InputSection *section) {
// sh_link field for SHT_GROUP sections should contain the section index of
// the symbol table.
os->link = in.symTab->getParent()->sectionIndex;
+ if (!section)
+ return;
+
// sh_info then contain index of an entry in symbol table section which
// provides signature of the section group.
ArrayRef<Symbol *> symbols = section->file->getSymbols();
// Some group members may be combined or discarded, so we need to compute the
// new size. The content will be rewritten in InputSection::copyShtGroup.
- std::unordered_set<uint32_t> seen;
+ DenseSet<uint32_t> seen;
ArrayRef<InputSectionBase *> sections = section->file->getSections();
for (const uint32_t &idx : section->getDataAs<uint32_t>().slice(1))
if (OutputSection *osec = sections[read32(&idx)]->getOutputSection())
// crtbegin files.
//
// Gcc uses any of crtbegin[<empty>|S|T].o.
-// Clang uses Gcc's plus clang_rt.crtbegin[<empty>|S|T][-<arch>|<empty>].o.
-
-static bool isCrtbegin(StringRef s) {
- static std::regex re(R"((clang_rt\.)?crtbegin[ST]?(-.*)?\.o)");
- s = sys::path::filename(s);
- return std::regex_match(s.begin(), s.end(), re);
-}
+// Clang uses Gcc's plus clang_rt.crtbegin[-<arch>|<empty>].o.
-static bool isCrtend(StringRef s) {
- static std::regex re(R"((clang_rt\.)?crtend[ST]?(-.*)?\.o)");
+static bool isCrt(StringRef s, StringRef beginEnd) {
s = sys::path::filename(s);
- return std::regex_match(s.begin(), s.end(), re);
+ if (!s.consume_back(".o"))
+ return false;
+ if (s.consume_front("clang_rt."))
+ return s.consume_front(beginEnd);
+ return s.consume_front(beginEnd) && s.size() <= 1;
}
// .ctors and .dtors are sorted by this order:
// are too many real-world use cases of .ctors, so we had no choice to
// support that with this rather ad-hoc semantics.
static bool compCtors(const InputSection *a, const InputSection *b) {
- bool beginA = isCrtbegin(a->file->getName());
- bool beginB = isCrtbegin(b->file->getName());
+ bool beginA = isCrt(a->file->getName(), "crtbegin");
+ bool beginB = isCrt(b->file->getName(), "crtbegin");
if (beginA != beginB)
return beginA;
- bool endA = isCrtend(a->file->getName());
- bool endB = isCrtend(b->file->getName());
+ bool endA = isCrt(a->file->getName(), "crtend");
+ bool endB = isCrt(b->file->getName(), "crtend");
if (endA != endB)
return endB;
return getPriority(a->name) > getPriority(b->name);
// Unfortunately, the rules are different from the one for .{init,fini}_array.
// Read the comment above.
void OutputSection::sortCtorsDtors() {
- assert(sectionCommands.size() == 1);
- auto *isd = cast<InputSectionDescription>(sectionCommands[0]);
+ assert(commands.size() == 1);
+ auto *isd = cast<InputSectionDescription>(commands[0]);
llvm::stable_sort(isd->sections, compCtors);
}
}
InputSection *elf::getFirstInputSection(const OutputSection *os) {
- for (BaseCommand *base : os->sectionCommands)
- if (auto *isd = dyn_cast<InputSectionDescription>(base))
+ for (SectionCommand *cmd : os->commands)
+ if (auto *isd = dyn_cast<InputSectionDescription>(cmd))
if (!isd->sections.empty())
return isd->sections[0];
return nullptr;
}
-std::vector<InputSection *> elf::getInputSections(const OutputSection *os) {
- std::vector<InputSection *> ret;
- for (BaseCommand *base : os->sectionCommands)
- if (auto *isd = dyn_cast<InputSectionDescription>(base))
- ret.insert(ret.end(), isd->sections.begin(), isd->sections.end());
- return ret;
+ArrayRef<InputSection *>
+elf::getInputSections(const OutputSection &os,
+ SmallVector<InputSection *, 0> &storage) {
+ ArrayRef<InputSection *> ret;
+ storage.clear();
+ for (SectionCommand *cmd : os.commands) {
+ auto *isd = dyn_cast<InputSectionDescription>(cmd);
+ if (!isd)
+ continue;
+ if (ret.empty()) {
+ ret = isd->sections;
+ } else {
+ if (storage.empty())
+ storage.assign(ret.begin(), ret.end());
+ storage.insert(storage.end(), isd->sections.begin(), isd->sections.end());
+ }
+ }
+ return storage.empty() ? ret : ArrayRef(storage);
}
// Sorts input sections by section name suffixes, so that .foo.N comes
void OutputSection::checkDynRelAddends(const uint8_t *bufStart) {
assert(config->writeAddends && config->checkDynamicRelocs);
assert(type == SHT_REL || type == SHT_RELA);
- std::vector<InputSection *> sections = getInputSections(this);
- parallelForEachN(0, sections.size(), [&](size_t i) {
+ SmallVector<InputSection *, 0> storage;
+ ArrayRef<InputSection *> sections = getInputSections(*this, storage);
+ parallelFor(0, sections.size(), [&](size_t i) {
// When linking with -r or --emit-relocs we might also call this function
// for input .rel[a].<sec> sections which we simply pass through to the
// output. We skip over those and only look at the synthetic relocation
if (!sec)
return;
for (const DynamicReloc &rel : sec->relocs) {
- int64_t addend = rel.computeAddend();
+ int64_t addend = rel.addend;
const OutputSection *relOsec = rel.inputSec->getOutputSection();
assert(relOsec != nullptr && "missing output section for relocation");
const uint8_t *relocTarget =
template void OutputSection::writeHeaderTo<ELF64LE>(ELF64LE::Shdr *Shdr);
template void OutputSection::writeHeaderTo<ELF64BE>(ELF64BE::Shdr *Shdr);
-template void OutputSection::writeTo<ELF32LE>(uint8_t *Buf);
-template void OutputSection::writeTo<ELF32BE>(uint8_t *Buf);
-template void OutputSection::writeTo<ELF64LE>(uint8_t *Buf);
-template void OutputSection::writeTo<ELF64BE>(uint8_t *Buf);
+template void OutputSection::writeTo<ELF32LE>(uint8_t *,
+ llvm::parallel::TaskGroup &);
+template void OutputSection::writeTo<ELF32BE>(uint8_t *,
+ llvm::parallel::TaskGroup &);
+template void OutputSection::writeTo<ELF64LE>(uint8_t *,
+ llvm::parallel::TaskGroup &);
+template void OutputSection::writeTo<ELF64BE>(uint8_t *,
+ llvm::parallel::TaskGroup &);
template void OutputSection::maybeCompress<ELF32LE>();
template void OutputSection::maybeCompress<ELF32BE>();
#ifndef LLD_ELF_OUTPUT_SECTIONS_H
#define LLD_ELF_OUTPUT_SECTIONS_H
-#include "Config.h"
#include "InputSection.h"
#include "LinkerScript.h"
-#include "Relocations.h"
#include "lld/Common/LLVM.h"
-#include "llvm/MC/StringTableBuilder.h"
-#include "llvm/Object/ELF.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Parallel.h"
+
#include <array>
-namespace lld {
-namespace elf {
+namespace lld::elf {
struct PhdrEntry;
-class InputSection;
-class InputSectionBase;
+
+struct CompressedData {
+ std::unique_ptr<SmallVector<uint8_t, 0>[]> shards;
+ uint32_t numShards = 0;
+ uint32_t checksum = 0;
+ uint64_t uncompressedSize;
+};
// This represents a section in an output file.
// It is composed of multiple InputSections.
// The writer creates multiple OutputSections and assign them unique,
// non-overlapping file offsets and VAs.
-class OutputSection final : public BaseCommand, public SectionBase {
+class OutputSection final : public SectionBase {
public:
OutputSection(StringRef name, uint32_t type, uint64_t flags);
return s->kind() == SectionBase::Output;
}
- static bool classof(const BaseCommand *c);
-
uint64_t getLMA() const { return ptLoad ? addr + ptLoad->lmaOffset : addr; }
template <typename ELFT> void writeHeaderTo(typename ELFT::Shdr *sHdr);
Expr alignExpr;
Expr lmaExpr;
Expr subalignExpr;
- std::vector<BaseCommand *> sectionCommands;
- std::vector<StringRef> phdrs;
- llvm::Optional<std::array<uint8_t, 4>> filler;
+ SmallVector<SectionCommand *, 0> commands;
+ SmallVector<StringRef, 0> phdrs;
+ std::optional<std::array<uint8_t, 4>> filler;
ConstraintKind constraint = ConstraintKind::NoConstraint;
std::string location;
std::string memoryRegionName;
std::string lmaRegionName;
bool nonAlloc = false;
- bool noload = false;
+ bool typeIsSet = false;
bool expressionsUseSymbols = false;
bool usedInExpression = false;
bool inOverlay = false;
// that wasn't needed). This is needed for orphan placement.
bool hasInputSections = false;
+ // The output section description is specified between DATA_SEGMENT_ALIGN and
+ // DATA_RELRO_END.
+ bool relro = false;
+
void finalize();
- template <class ELFT> void writeTo(uint8_t *buf);
+ template <class ELFT>
+ void writeTo(uint8_t *buf, llvm::parallel::TaskGroup &tg);
// Check that the addends for dynamic relocations were written correctly.
void checkDynRelAddends(const uint8_t *bufStart);
template <class ELFT> void maybeCompress();
void sortCtorsDtors();
private:
+ SmallVector<InputSection *, 0> storage;
+
// Used for implementation of --compress-debug-sections option.
- std::vector<uint8_t> zDebugHeader;
- llvm::SmallVector<char, 0> compressedData;
+ CompressedData compressed;
std::array<uint8_t, 4> getFiller();
};
+struct OutputDesc final : SectionCommand {
+ OutputSection osec;
+ OutputDesc(StringRef name, uint32_t type, uint64_t flags)
+ : SectionCommand(OutputSectionKind), osec(name, type, flags) {}
+
+ static bool classof(const SectionCommand *c) {
+ return c->kind == OutputSectionKind;
+ }
+};
+
int getPriority(StringRef s);
InputSection *getFirstInputSection(const OutputSection *os);
-std::vector<InputSection *> getInputSections(const OutputSection *os);
+llvm::ArrayRef<InputSection *>
+getInputSections(const OutputSection &os,
+ SmallVector<InputSection *, 0> &storage);
// All output sections that are handled by the linker specially are
// globally accessible. Writer initializes them, so don't use them
// until Writer is initialized.
struct Out {
static uint8_t *bufferStart;
- static uint8_t first;
static PhdrEntry *tlsPhdr;
static OutputSection *elfHeader;
static OutputSection *programHeaders;
uint64_t getHeaderSize();
-extern std::vector<OutputSection *> outputSections;
-} // namespace elf
-} // namespace lld
+LLVM_LIBRARY_VISIBILITY extern llvm::SmallVector<OutputSection *, 0>
+ outputSections;
+} // namespace lld::elf
#endif
#include "lld/Common/LLVM.h"
#include "llvm/ADT/DenseMap.h"
-#include <map>
+#include "llvm/ADT/STLExtras.h"
#include <vector>
-namespace lld {
-namespace elf {
+namespace lld::elf {
class Symbol;
class InputSection;
class InputSectionBase;
R_PC,
R_PLT,
R_PLT_PC,
+ R_PLT_GOTPLT,
+ R_RELAX_HINT,
R_RELAX_GOT_PC,
R_RELAX_GOT_PC_NOPIC,
R_RELAX_TLS_GD_TO_IE,
R_TLSDESC,
R_TLSDESC_CALL,
R_TLSDESC_PC,
+ R_TLSDESC_GOTPLT,
R_TLSGD_GOT,
R_TLSGD_GOTPLT,
R_TLSGD_PC,
// jump instruction opcodes at basic block boundaries and are particularly
// useful when basic block sections are enabled.
struct JumpInstrMod {
- JumpModType original;
uint64_t offset;
+ JumpModType original;
unsigned size;
};
// This function writes undefined symbol diagnostics to an internal buffer.
// Call reportUndefinedSymbols() after calling scanRelocations() to emit
// the diagnostics.
-template <class ELFT> void scanRelocations(InputSectionBase &);
-
-template <class ELFT> void reportUndefinedSymbols();
+template <class ELFT> void scanRelocations();
+void reportUndefinedSymbols();
+void postScanRelocations();
void hexagonTLSSymbolUpdate(ArrayRef<OutputSection *> outputSections);
bool hexagonNeedsTLSSymbol(ArrayRef<OutputSection *> outputSections);
class ThunkCreator {
public:
// Return true if Thunks have been added to OutputSections
- bool createThunks(ArrayRef<OutputSection *> outputSections);
-
- // The number of completed passes of createThunks this permits us
- // to do one time initialization on Pass 0 and put a limit on the
- // number of times it can be called to prevent infinite loops.
- uint32_t pass = 0;
+ bool createThunks(uint32_t pass, ArrayRef<OutputSection *> outputSections);
private:
void mergeThunks(ArrayRef<OutputSection *> outputSections);
// so we need to make sure that there is only one of them.
// The Mips LA25 Thunk is an example of an inline ThunkSection.
llvm::DenseMap<InputSection *, ThunkSection *> thunkedSections;
+
+ // The number of completed passes of createThunks this permits us
+ // to do one time initialization on Pass 0 and put a limit on the
+ // number of times it can be called to prevent infinite loops.
+ uint32_t pass = 0;
};
// Return a int64_t to make sure we get the sign extension out of the way as
}
return rels;
}
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
#include "ScriptLexer.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/ADT/Twine.h"
+#include "llvm/Support/ErrorHandling.h"
+#include <algorithm>
using namespace llvm;
using namespace lld;
continue;
}
- // ">foo" is parsed to ">" and "foo", but ">>" is parsed to ">>".
- // "|", "||", "&" and "&&" are different operators.
- if (s.startswith("<<") || s.startswith("<=") || s.startswith(">>") ||
- s.startswith(">=") || s.startswith("||") || s.startswith("&&")) {
+ // Some operators form separate tokens.
+ if (s.startswith("<<=") || s.startswith(">>=")) {
+ vec.push_back(s.substr(0, 3));
+ s = s.substr(3);
+ continue;
+ }
+ if (s.size() > 1 && ((s[1] == '=' && strchr("*/+-<>&|", s[0])) ||
+ (s[0] == s[1] && strchr("<>&|", s[0])))) {
vec.push_back(s.substr(0, 2));
s = s.substr(2);
continue;
// Split a given string as an expression.
// This function returns "3", "*" and "5" for "3*5" for example.
static std::vector<StringRef> tokenizeExpr(StringRef s) {
- StringRef ops = "+-*/:!~=<>"; // List of operators
+ StringRef ops = "!~*/+-<>?:="; // List of operators
// Quoted strings are literal strings, so we don't want to split it.
if (s.startswith("\""))
#include "lld/Common/LLVM.h"
#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/MemoryBuffer.h"
-#include <utility>
+#include "llvm/Support/MemoryBufferRef.h"
#include <vector>
-namespace lld {
-namespace elf {
+namespace lld::elf {
class ScriptLexer {
public:
size_t getColumnNumber();
};
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
#define LLD_ELF_SCRIPT_PARSER_H
#include "lld/Common/LLVM.h"
-#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/MemoryBufferRef.h"
-namespace lld {
-namespace elf {
+namespace lld::elf {
// Parses a linker script. Calling this function updates
// lld::elf::config and lld::elf::script.
bool hasWildcard(StringRef s);
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
#ifndef LLD_ELF_SYMBOL_TABLE_H
#define LLD_ELF_SYMBOL_TABLE_H
-#include "InputFiles.h"
#include "Symbols.h"
-#include "lld/Common/Strings.h"
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Compiler.h"
-namespace lld {
-namespace elf {
+namespace lld::elf {
+
+class InputFile;
+class SharedFile;
// SymbolTable is a bucket of all known symbols, including defined,
// undefined, or lazy symbols (the last one is symbols in archive
// add*() functions, which are called by input files as they are parsed. There
// is one add* function per symbol type.
class SymbolTable {
- struct FilterOutPlaceholder {
- bool operator()(Symbol *S) const { return !S->isPlaceholder(); }
- };
- using iterator = llvm::filter_iterator<std::vector<Symbol *>::const_iterator,
- FilterOutPlaceholder>;
-
public:
- llvm::iterator_range<iterator> symbols() const {
- return llvm::make_filter_range(symVector, FilterOutPlaceholder());
- }
+ ArrayRef<Symbol *> getSymbols() const { return symVector; }
void wrap(Symbol *sym, Symbol *real, Symbol *wrap);
Symbol *insert(StringRef name);
- Symbol *addSymbol(const Symbol &newSym);
+ template <typename T> Symbol *addSymbol(const T &newSym) {
+ Symbol *sym = insert(newSym.getName());
+ sym->resolve(newSym);
+ return sym;
+ }
+ Symbol *addAndCheckDuplicate(const Defined &newSym);
void scanVersionScript();
void handleDynamicList();
// Set of .so files to not link the same shared object file more than once.
- llvm::DenseMap<StringRef, SharedFile *> soNames;
+ llvm::DenseMap<llvm::CachedHashStringRef, SharedFile *> soNames;
// Comdat groups define "link once" sections. If two comdat groups have the
// same name, only one of them is linked, and the other is ignored. This map
llvm::DenseMap<llvm::CachedHashStringRef, const InputFile *> comdatGroups;
private:
- std::vector<Symbol *> findByVersion(SymbolVersion ver);
- std::vector<Symbol *> findAllByVersion(SymbolVersion ver,
- bool includeNonDefault);
+ SmallVector<Symbol *, 0> findByVersion(SymbolVersion ver);
+ SmallVector<Symbol *, 0> findAllByVersion(SymbolVersion ver,
+ bool includeNonDefault);
- llvm::StringMap<std::vector<Symbol *>> &getDemangledSyms();
+ llvm::StringMap<SmallVector<Symbol *, 0>> &getDemangledSyms();
bool assignExactVersion(SymbolVersion ver, uint16_t versionId,
StringRef versionName, bool includeNonDefault);
void assignWildcardVersion(SymbolVersion ver, uint16_t versionId,
// FIXME: Experiment with passing in a custom hashing or sorting the symbols
// once symbol resolution is finished.
llvm::DenseMap<llvm::CachedHashStringRef, int> symMap;
- std::vector<Symbol *> symVector;
+ SmallVector<Symbol *, 0> symVector;
// A map from demangled symbol names to their symbol objects.
// This mapping is 1:N because two symbols with different versions
// can have the same name. We use this map to handle "extern C++ {}"
// directive in version scripts.
- llvm::Optional<llvm::StringMap<std::vector<Symbol *>>> demangledSyms;
+ std::optional<llvm::StringMap<SmallVector<Symbol *, 0>>> demangledSyms;
};
-extern SymbolTable *symtab;
+LLVM_LIBRARY_VISIBILITY extern SymbolTable symtab;
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
llvm_unreachable("unknown target machine");
}
-template <class ELFT> static ErrorPlace getErrPlace(const uint8_t *loc) {
+ErrorPlace elf::getErrorPlace(const uint8_t *loc) {
assert(loc != nullptr);
- for (InputSectionBase *d : inputSections) {
- auto *isec = cast<InputSection>(d);
- if (!isec->getParent() || (isec->type & SHT_NOBITS))
+ for (InputSectionBase *d : ctx.inputSections) {
+ auto *isec = dyn_cast<InputSection>(d);
+ if (!isec || !isec->getParent() || (isec->type & SHT_NOBITS))
continue;
const uint8_t *isecLoc =
Out::bufferStart
? (Out::bufferStart + isec->getParent()->offset + isec->outSecOff)
- : isec->data().data();
+ : isec->contentMaybeDecompress().data();
if (isecLoc == nullptr) {
assert(isa<SyntheticSection>(isec) && "No data but not synthetic?");
continue;
}
- if (isecLoc <= loc && loc < isecLoc + isec->getSize())
- return {isec, isec->template getLocation<ELFT>(loc - isecLoc) + ": "};
+ if (isecLoc <= loc && loc < isecLoc + isec->getSize()) {
+ std::string objLoc = isec->getLocation(loc - isecLoc);
+ // Return object file location and source file location.
+ // TODO: Refactor getSrcMsg not to take a variable.
+ Undefined dummy(nullptr, "", STB_LOCAL, 0, 0);
+ return {isec, objLoc + ": ",
+ isec->file ? isec->getSrcMsg(dummy, loc - isecLoc) : ""};
+ }
}
return {};
}
-ErrorPlace elf::getErrorPlace(const uint8_t *loc) {
- switch (config->ekind) {
- case ELF32LEKind:
- return getErrPlace<ELF32LE>(loc);
- case ELF32BEKind:
- return getErrPlace<ELF32BE>(loc);
- case ELF64LEKind:
- return getErrPlace<ELF64LE>(loc);
- case ELF64BEKind:
- return getErrPlace<ELF64BE>(loc);
- default:
- llvm_unreachable("unknown ELF type");
- }
-}
-
TargetInfo::~TargetInfo() {}
int64_t TargetInfo::getImplicitAddend(const uint8_t *buf, RelType type) const {
return R_GOT_PC;
}
-void TargetInfo::relaxGot(uint8_t *loc, const Relocation &rel,
- uint64_t val) const {
- llvm_unreachable("Should not have claimed to be relaxable");
-}
-
-void TargetInfo::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const {
- llvm_unreachable("Should not have claimed to be relaxable");
-}
-
-void TargetInfo::relaxTlsGdToIe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const {
- llvm_unreachable("Should not have claimed to be relaxable");
-}
-
-void TargetInfo::relaxTlsIeToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const {
- llvm_unreachable("Should not have claimed to be relaxable");
-}
-
-void TargetInfo::relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const {
- llvm_unreachable("Should not have claimed to be relaxable");
+void TargetInfo::relocateAlloc(InputSectionBase &sec, uint8_t *buf) const {
+ const unsigned bits = config->is64 ? 64 : 32;
+ uint64_t secAddr = sec.getOutputSection()->addr;
+ if (auto *s = dyn_cast<InputSection>(&sec))
+ secAddr += s->outSecOff;
+ for (const Relocation &rel : sec.relocs()) {
+ uint8_t *loc = buf + rel.offset;
+ const uint64_t val = SignExtend64(
+ sec.getRelocTargetVA(sec.file, rel.type, rel.addend,
+ secAddr + rel.offset, *rel.sym, rel.expr),
+ bits);
+ if (rel.expr != R_RELAX_HINT)
+ relocate(loc, rel, val);
+ }
}
uint64_t TargetInfo::getImageBase() const {
- // Use -image-base if set. Fall back to the target default if not.
+ // Use --image-base if set. Fall back to the target default if not.
if (config->imageBase)
return *config->imageBase;
return config->isPic ? 0 : defaultImageBase;
#ifndef LLD_ELF_TARGET_H
#define LLD_ELF_TARGET_H
+#include "Config.h"
#include "InputSection.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/Object/ELF.h"
+#include "llvm/Support/Compiler.h"
#include "llvm/Support/MathExtras.h"
#include <array>
void relocateNoSym(uint8_t *loc, RelType type, uint64_t val) const {
relocate(loc, Relocation{R_NONE, type, 0, 0, nullptr}, val);
}
+ virtual void relocateAlloc(InputSectionBase &sec, uint8_t *buf) const;
+
+ // Do a linker relaxation pass and return true if we changed something.
+ virtual bool relaxOnce(int pass) const { return false; }
virtual void applyJumpInstrMod(uint8_t *loc, JumpModType type,
JumpModType val) const {}
uint64_t getImageBase() const;
// True if _GLOBAL_OFFSET_TABLE_ is relative to .got.plt, false if .got.
- bool gotBaseSymInGotPlt = true;
+ bool gotBaseSymInGotPlt = false;
+ static constexpr RelType noneRel = 0;
RelType copyRel;
RelType gotRel;
- RelType noneRel;
RelType pltRel;
RelType relativeRel;
RelType iRelativeRel;
// Stores the NOP instructions of different sizes for the target and is used
// to pad sections that are relaxed.
- llvm::Optional<std::vector<std::vector<uint8_t>>> nopInstrs;
+ std::optional<std::vector<std::vector<uint8_t>>> nopInstrs;
// If a target needs to rewrite calls to __morestack to instead call
// __morestack_non_split when a split-stack enabled caller calls a
virtual RelExpr adjustTlsExpr(RelType type, RelExpr expr) const;
virtual RelExpr adjustGotPcExpr(RelType type, int64_t addend,
const uint8_t *loc) const;
- virtual void relaxGot(uint8_t *loc, const Relocation &rel,
- uint64_t val) const;
- virtual void relaxTlsGdToIe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const;
- virtual void relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const;
- virtual void relaxTlsIeToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const;
- virtual void relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
- uint64_t val) const;
protected:
// On FreeBSD x86_64 the first page cannot be mmaped.
struct ErrorPlace {
InputSectionBase *isec;
std::string loc;
+ std::string srcLoc;
};
// Returns input section and corresponding source string for the given location.
void writePPC32GlinkSection(uint8_t *buf, size_t numEntries);
-bool tryRelaxPPC64TocIndirection(const Relocation &rel, uint8_t *bufLoc);
unsigned getPPCDFormOp(unsigned secondaryOp);
// In the PowerPC64 Elf V2 abi a function can have 2 entry points. The first
// to the local entry-point.
unsigned getPPC64GlobalEntryToLocalEntryOffset(uint8_t stOther);
-// Returns true if a relocation is a small code model relocation that accesses
-// the .toc section.
-bool isPPC64SmallCodeModelTocReloc(RelType type);
-
// Write a prefixed instruction, which is a 4-byte prefix followed by a 4-byte
// instruction (regardless of endianness). Therefore, the prefix is always in
// lower memory than the instruction.
void addPPC64SaveRestore();
uint64_t getPPC64TocBase();
uint64_t getAArch64Page(uint64_t expr);
+void riscvFinalizeRelax(int passes);
+void mergeRISCVAttributesSections();
-extern const TargetInfo *target;
+LLVM_LIBRARY_VISIBILITY extern const TargetInfo *target;
TargetInfo *getTarget();
template <class ELFT> bool isMipsPIC(const Defined *sym);
} // namespace elf
} // namespace lld
+#ifdef __clang__
+#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
+#endif
+#define invokeELFT(f, ...) \
+ switch (config->ekind) { \
+ case ELF32LEKind: \
+ f<ELF32LE>(__VA_ARGS__); \
+ break; \
+ case ELF32BEKind: \
+ f<ELF32BE>(__VA_ARGS__); \
+ break; \
+ case ELF64LEKind: \
+ f<ELF64LE>(__VA_ARGS__); \
+ break; \
+ case ELF64BEKind: \
+ f<ELF64BE>(__VA_ARGS__); \
+ break; \
+ default: \
+ llvm_unreachable("unknown config->ekind"); \
+ }
+
#endif
#include "Thunks.h"
#include "Config.h"
+#include "InputFiles.h"
#include "InputSection.h"
#include "OutputSections.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Support/Casting.h"
-#include "llvm/Support/Endian.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include <cstdint>
void addSymbols(ThunkSection &isec) override;
};
-// Implementations of Thunks for older Arm architectures that do not support
-// the movt/movw instructions. These thunks require at least Architecture v5
-// as used on processors such as the Arm926ej-s. There are no Thumb entry
-// points as there is no Thumb branch instruction on these architecture that
-// can result in a thunk
-class ARMV5ABSLongThunk final : public ARMThunk {
+// Implementations of Thunks for Arm v6-M. Only Thumb instructions are permitted
+class ThumbV6MABSLongThunk final : public ThumbThunk {
+public:
+ ThumbV6MABSLongThunk(Symbol &dest, int64_t addend)
+ : ThumbThunk(dest, addend) {}
+
+ uint32_t sizeLong() override { return 12; }
+ void writeLong(uint8_t *buf) override;
+ void addSymbols(ThunkSection &isec) override;
+};
+
+class ThumbV6MPILongThunk final : public ThumbThunk {
+public:
+ ThumbV6MPILongThunk(Symbol &dest, int64_t addend)
+ : ThumbThunk(dest, addend) {}
+
+ uint32_t sizeLong() override { return 16; }
+ void writeLong(uint8_t *buf) override;
+ void addSymbols(ThunkSection &isec) override;
+};
+
+// Architectures v4, v5 and v6 do not support the movt/movw instructions. v5 and
+// v6 support BLX to which BL instructions can be rewritten inline. There are no
+// Thumb entrypoints for v5 and v6 as there is no Thumb branch instruction on
+// these architecture that can result in a thunk.
+
+// LDR on v5 and v6 can switch processor state, so for v5 and v6,
+// ARMV5LongLdrPcThunk can be used for both Arm->Arm and Arm->Thumb calls. v4
+// can also use this thunk, but only for Arm->Arm calls.
+class ARMV5LongLdrPcThunk final : public ARMThunk {
public:
- ARMV5ABSLongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
+ ARMV5LongLdrPcThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
uint32_t sizeLong() override { return 8; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
- bool isCompatibleWith(const InputSection &isec,
- const Relocation &rel) const override;
};
-class ARMV5PILongThunk final : public ARMThunk {
+// Implementations of Thunks for v4. BLX is not supported, and loads
+// will not invoke Arm/Thumb state changes.
+class ARMV4PILongBXThunk final : public ARMThunk {
public:
- ARMV5PILongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
+ ARMV4PILongBXThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
uint32_t sizeLong() override { return 16; }
void writeLong(uint8_t *buf) override;
void addSymbols(ThunkSection &isec) override;
- bool isCompatibleWith(const InputSection &isec,
- const Relocation &rel) const override;
};
-// Implementations of Thunks for Arm v6-M. Only Thumb instructions are permitted
-class ThumbV6MABSLongThunk final : public ThumbThunk {
+class ARMV4PILongThunk final : public ARMThunk {
public:
- ThumbV6MABSLongThunk(Symbol &dest, int64_t addend)
+ ARMV4PILongThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
+
+ uint32_t sizeLong() override { return 12; }
+ void writeLong(uint8_t *buf) override;
+ void addSymbols(ThunkSection &isec) override;
+};
+
+class ThumbV4PILongBXThunk final : public ThumbThunk {
+public:
+ ThumbV4PILongBXThunk(Symbol &dest, int64_t addend)
+ : ThumbThunk(dest, addend) {}
+
+ uint32_t sizeLong() override { return 16; }
+ void writeLong(uint8_t *buf) override;
+ void addSymbols(ThunkSection &isec) override;
+};
+
+class ThumbV4PILongThunk final : public ThumbThunk {
+public:
+ ThumbV4PILongThunk(Symbol &dest, int64_t addend)
+ : ThumbThunk(dest, addend) {}
+
+ uint32_t sizeLong() override { return 20; }
+ void writeLong(uint8_t *buf) override;
+ void addSymbols(ThunkSection &isec) override;
+};
+
+class ARMV4ABSLongBXThunk final : public ARMThunk {
+public:
+ ARMV4ABSLongBXThunk(Symbol &dest, int64_t addend) : ARMThunk(dest, addend) {}
+
+ uint32_t sizeLong() override { return 12; }
+ void writeLong(uint8_t *buf) override;
+ void addSymbols(ThunkSection &isec) override;
+};
+
+class ThumbV4ABSLongBXThunk final : public ThumbThunk {
+public:
+ ThumbV4ABSLongBXThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 12; }
void addSymbols(ThunkSection &isec) override;
};
-class ThumbV6MPILongThunk final : public ThumbThunk {
+class ThumbV4ABSLongThunk final : public ThumbThunk {
public:
- ThumbV6MPILongThunk(Symbol &dest, int64_t addend)
+ ThumbV4ABSLongThunk(Symbol &dest, int64_t addend)
: ThumbThunk(dest, addend) {}
uint32_t sizeLong() override { return 16; }
PPC64PILongBranchThunk(Symbol &dest, int64_t addend)
: PPC64LongBranchThunk(dest, addend) {
assert(!dest.isPreemptible);
- if (Optional<uint32_t> index =
+ if (std::optional<uint32_t> index =
in.ppc64LongBranchTarget->addEntry(&dest, addend)) {
mainPart->relaDyn->addRelativeReloc(
- target->relativeRel, in.ppc64LongBranchTarget, *index * UINT64_C(8),
+ target->relativeRel, *in.ppc64LongBranchTarget, *index * UINT64_C(8),
dest, addend + getPPC64GlobalEntryToLocalEntryOffset(dest.stOther),
target->symbolicRel, R_ABS);
}
}
};
-// A bl instruction uses a signed 24 bit offset, with an implicit 4 byte
-// alignment. This gives a possible 26 bits of 'reach'. If the caller and
-// callee do not use toc and the call offset is larger than 26 bits,
-// we need to emit a pc-rel based long-branch thunk. The target address of
-// the callee is computed with a PC-relative offset.
-class PPC64PCRelLongBranchThunk final : public Thunk {
-public:
- PPC64PCRelLongBranchThunk(Symbol &dest, int64_t addend)
- : Thunk(dest, addend) {
- alignment = 16;
- }
- uint32_t size() override { return 32; }
- void writeTo(uint8_t *buf) override;
- void addSymbols(ThunkSection &isec) override;
- bool isCompatibleWith(const InputSection &isec,
- const Relocation &rel) const override;
-};
-
} // end anonymous namespace
Defined *Thunk::addSymbol(StringRef name, uint8_t type, uint64_t value,
}
void AArch64ABSLongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__AArch64AbsLongThunk_" + destination.getName()),
+ addSymbol(saver().save("__AArch64AbsLongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$x", STT_NOTYPE, 0, isec);
addSymbol("$d", STT_NOTYPE, 8, isec);
}
void AArch64ADRPThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__AArch64ADRPThunk_" + destination.getName()), STT_FUNC,
- 0, isec);
+ addSymbol(saver().save("__AArch64ADRPThunk_" + destination.getName()),
+ STT_FUNC, 0, isec);
addSymbol("$x", STT_NOTYPE, 0, isec);
}
bool ARMThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
+ // v4T does not have BLX, so also deny R_ARM_THM_CALL
+ if (!config->armHasBlx && rel.type == R_ARM_THM_CALL)
+ return false;
+
// Thumb branch relocations can't use BLX
return rel.type != R_ARM_THM_JUMP19 && rel.type != R_ARM_THM_JUMP24;
}
-// This function returns true if the target is Thumb and is within 2^25, and
-// it has not previously returned false (see comment for mayUseShortThunk).
+// This function returns true if:
+// the target is Thumb
+// && is within branch range
+// && this function has not previously returned false
+// (see comment for mayUseShortThunk)
+// && the arch supports Thumb branch range extension.
bool ThumbThunk::getMayUseShortThunk() {
- if (!mayUseShortThunk)
+ if (!mayUseShortThunk || !config->armJ1J2BranchEncoding)
return false;
uint64_t s = getARMThunkDestVA(destination);
if ((s & 1) == 0) {
bool ThumbThunk::isCompatibleWith(const InputSection &isec,
const Relocation &rel) const {
+ // v4T does not have BLX, so also deny R_ARM_CALL
+ if (!config->armHasBlx && rel.type == R_ARM_CALL)
+ return false;
+
// ARM branch relocations can't use BLX
return rel.type != R_ARM_JUMP24 && rel.type != R_ARM_PC24 && rel.type != R_ARM_PLT32;
}
}
void ARMV7ABSLongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__ARMv7ABSLongThunk_" + destination.getName()),
+ addSymbol(saver().save("__ARMv7ABSLongThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
}
}
void ThumbV7ABSLongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__Thumbv7ABSLongThunk_" + destination.getName()),
+ addSymbol(saver().save("__Thumbv7ABSLongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
}
}
void ARMV7PILongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__ARMV7PILongThunk_" + destination.getName()), STT_FUNC,
- 0, isec);
+ addSymbol(saver().save("__ARMV7PILongThunk_" + destination.getName()),
+ STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
}
}
void ThumbV7PILongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__ThumbV7PILongThunk_" + destination.getName()),
+ addSymbol(saver().save("__ThumbV7PILongThunk_" + destination.getName()),
+ STT_FUNC, 1, isec);
+ addSymbol("$t", STT_NOTYPE, 0, isec);
+}
+
+void ThumbV6MABSLongThunk::writeLong(uint8_t *buf) {
+ // Most Thumb instructions cannot access the high registers r8 - r15. As the
+ // only register we can corrupt is r12 we must instead spill a low register
+ // to the stack to use as a scratch register. We push r1 even though we
+ // don't need to get some space to use for the return address.
+ const uint8_t data[] = {
+ 0x03, 0xb4, // push {r0, r1} ; Obtain scratch registers
+ 0x01, 0x48, // ldr r0, [pc, #4] ; L1
+ 0x01, 0x90, // str r0, [sp, #4] ; SP + 4 = S
+ 0x01, 0xbd, // pop {r0, pc} ; restore r0 and branch to dest
+ 0x00, 0x00, 0x00, 0x00 // L1: .word S
+ };
+ uint64_t s = getARMThunkDestVA(destination);
+ memcpy(buf, data, sizeof(data));
+ target->relocateNoSym(buf + 8, R_ARM_ABS32, s);
+}
+
+void ThumbV6MABSLongThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__Thumbv6MABSLongThunk_" + destination.getName()),
+ STT_FUNC, 1, isec);
+ addSymbol("$t", STT_NOTYPE, 0, isec);
+ addSymbol("$d", STT_NOTYPE, 8, isec);
+}
+
+void ThumbV6MPILongThunk::writeLong(uint8_t *buf) {
+ // Most Thumb instructions cannot access the high registers r8 - r15. As the
+ // only register we can corrupt is ip (r12) we must instead spill a low
+ // register to the stack to use as a scratch register.
+ const uint8_t data[] = {
+ 0x01, 0xb4, // P: push {r0} ; Obtain scratch register
+ 0x02, 0x48, // ldr r0, [pc, #8] ; L2
+ 0x84, 0x46, // mov ip, r0 ; high to low register
+ 0x01, 0xbc, // pop {r0} ; restore scratch register
+ 0xe7, 0x44, // L1: add pc, ip ; transfer control
+ 0xc0, 0x46, // nop ; pad to 4-byte boundary
+ 0x00, 0x00, 0x00, 0x00, // L2: .word S - (P + (L1 - P) + 4)
+ };
+ uint64_t s = getARMThunkDestVA(destination);
+ uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
+ memcpy(buf, data, sizeof(data));
+ target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 12);
+}
+
+void ThumbV6MPILongThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__Thumbv6MPILongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
+ addSymbol("$d", STT_NOTYPE, 12, isec);
}
-void ARMV5ABSLongThunk::writeLong(uint8_t *buf) {
+void ARMV5LongLdrPcThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
- 0x04, 0xf0, 0x1f, 0xe5, // ldr pc, [pc,#-4] ; L1
+ 0x04, 0xf0, 0x1f, 0xe5, // ldr pc, [pc,#-4] ; L1
0x00, 0x00, 0x00, 0x00, // L1: .word S
};
memcpy(buf, data, sizeof(data));
target->relocateNoSym(buf + 4, R_ARM_ABS32, getARMThunkDestVA(destination));
}
-void ARMV5ABSLongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__ARMv5ABSLongThunk_" + destination.getName()),
+void ARMV5LongLdrPcThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__ARMv5LongLdrPcThunk_" + destination.getName()),
STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
addSymbol("$d", STT_NOTYPE, 4, isec);
}
-bool ARMV5ABSLongThunk::isCompatibleWith(const InputSection &isec,
- const Relocation &rel) const {
- // Thumb branch relocations can't use BLX
- return rel.type != R_ARM_THM_JUMP19 && rel.type != R_ARM_THM_JUMP24;
+void ARMV4ABSLongBXThunk::writeLong(uint8_t *buf) {
+ const uint8_t data[] = {
+ 0x00, 0xc0, 0x9f, 0xe5, // ldr r12, [pc] ; L1
+ 0x1c, 0xff, 0x2f, 0xe1, // bx r12
+ 0x00, 0x00, 0x00, 0x00, // L1: .word S
+ };
+ memcpy(buf, data, sizeof(data));
+ target->relocateNoSym(buf + 8, R_ARM_ABS32, getARMThunkDestVA(destination));
+}
+
+void ARMV4ABSLongBXThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__ARMv4ABSLongBXThunk_" + destination.getName()),
+ STT_FUNC, 0, isec);
+ addSymbol("$a", STT_NOTYPE, 0, isec);
+ addSymbol("$d", STT_NOTYPE, 8, isec);
+}
+
+void ThumbV4ABSLongBXThunk::writeLong(uint8_t *buf) {
+ const uint8_t data[] = {
+ 0x78, 0x47, // bx pc
+ 0xfd, 0xe7, // b #-6 ; Arm recommended sequence to follow bx pc
+ 0x04, 0xf0, 0x1f, 0xe5, // ldr pc, [pc, #-4] ; L1
+ 0x00, 0x00, 0x00, 0x00, // L1: .word S
+ };
+ memcpy(buf, data, sizeof(data));
+ target->relocateNoSym(buf + 8, R_ARM_ABS32, getARMThunkDestVA(destination));
+}
+
+void ThumbV4ABSLongBXThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__Thumbv4ABSLongBXThunk_" + destination.getName()),
+ STT_FUNC, 1, isec);
+ addSymbol("$t", STT_NOTYPE, 0, isec);
+ addSymbol("$a", STT_NOTYPE, 4, isec);
+ addSymbol("$d", STT_NOTYPE, 8, isec);
+}
+
+void ThumbV4ABSLongThunk::writeLong(uint8_t *buf) {
+ const uint8_t data[] = {
+ 0x78, 0x47, // bx pc
+ 0xfd, 0xe7, // b #-6 ; Arm recommended sequence to follow bx pc
+ 0x00, 0xc0, 0x9f, 0xe5, // ldr r12, [pc] ; L1
+ 0x1c, 0xff, 0x2f, 0xe1, // bx r12
+ 0x00, 0x00, 0x00, 0x00, // L1: .word S
+ };
+ memcpy(buf, data, sizeof(data));
+ target->relocateNoSym(buf + 12, R_ARM_ABS32, getARMThunkDestVA(destination));
+}
+
+void ThumbV4ABSLongThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__Thumbv4ABSLongThunk_" + destination.getName()),
+ STT_FUNC, 1, isec);
+ addSymbol("$t", STT_NOTYPE, 0, isec);
+ addSymbol("$a", STT_NOTYPE, 4, isec);
+ addSymbol("$d", STT_NOTYPE, 12, isec);
}
-void ARMV5PILongThunk::writeLong(uint8_t *buf) {
+void ARMV4PILongBXThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
0x04, 0xc0, 0x9f, 0xe5, // P: ldr ip, [pc,#4] ; L2
0x0c, 0xc0, 0x8f, 0xe0, // L1: add ip, pc, ip
target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 12);
}
-void ARMV5PILongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__ARMV5PILongThunk_" + destination.getName()), STT_FUNC,
- 0, isec);
+void ARMV4PILongBXThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__ARMv4PILongBXThunk_" + destination.getName()),
+ STT_FUNC, 0, isec);
addSymbol("$a", STT_NOTYPE, 0, isec);
addSymbol("$d", STT_NOTYPE, 12, isec);
}
-bool ARMV5PILongThunk::isCompatibleWith(const InputSection &isec,
- const Relocation &rel) const {
- // Thumb branch relocations can't use BLX
- return rel.type != R_ARM_THM_JUMP19 && rel.type != R_ARM_THM_JUMP24;
+void ARMV4PILongThunk::writeLong(uint8_t *buf) {
+ const uint8_t data[] = {
+ 0x00, 0xc0, 0x9f, 0xe5, // P: ldr ip, [pc] ; L2
+ 0x0c, 0xf0, 0x8f, 0xe0, // L1: add pc, pc, r12
+ 0x00, 0x00, 0x00, 0x00, // L2: .word S - (P + (L1 - P) + 8)
+ };
+ uint64_t s = getARMThunkDestVA(destination);
+ uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
+ memcpy(buf, data, sizeof(data));
+ target->relocateNoSym(buf + 8, R_ARM_REL32, s - p - 12);
}
-void ThumbV6MABSLongThunk::writeLong(uint8_t *buf) {
- // Most Thumb instructions cannot access the high registers r8 - r15. As the
- // only register we can corrupt is r12 we must instead spill a low register
- // to the stack to use as a scratch register. We push r1 even though we
- // don't need to get some space to use for the return address.
+void ARMV4PILongThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__ARMv4PILongThunk_" + destination.getName()),
+ STT_FUNC, 0, isec);
+ addSymbol("$a", STT_NOTYPE, 0, isec);
+ addSymbol("$d", STT_NOTYPE, 8, isec);
+}
+
+void ThumbV4PILongBXThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
- 0x03, 0xb4, // push {r0, r1} ; Obtain scratch registers
- 0x01, 0x48, // ldr r0, [pc, #4] ; L1
- 0x01, 0x90, // str r0, [sp, #4] ; SP + 4 = S
- 0x01, 0xbd, // pop {r0, pc} ; restore r0 and branch to dest
- 0x00, 0x00, 0x00, 0x00 // L1: .word S
+ 0x78, 0x47, // P: bx pc
+ 0xfd, 0xe7, // b #-6 ; Arm recommended sequence to follow bx pc
+ 0x00, 0xc0, 0x9f, 0xe5, // ldr r12, [pc] ; L2
+ 0x0f, 0xf0, 0x8c, 0xe0, // L1: add pc, r12, pc
+ 0x00, 0x00, 0x00, 0x00, // L2: .word S - (P + (L1 - P) + 8)
};
uint64_t s = getARMThunkDestVA(destination);
+ uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
memcpy(buf, data, sizeof(data));
- target->relocateNoSym(buf + 8, R_ARM_ABS32, s);
+ target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 16);
}
-void ThumbV6MABSLongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__Thumbv6MABSLongThunk_" + destination.getName()),
+void ThumbV4PILongBXThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__Thumbv4PILongBXThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
- addSymbol("$d", STT_NOTYPE, 8, isec);
+ addSymbol("$a", STT_NOTYPE, 4, isec);
+ addSymbol("$d", STT_NOTYPE, 12, isec);
}
-void ThumbV6MPILongThunk::writeLong(uint8_t *buf) {
- // Most Thumb instructions cannot access the high registers r8 - r15. As the
- // only register we can corrupt is ip (r12) we must instead spill a low
- // register to the stack to use as a scratch register.
+void ThumbV4PILongThunk::writeLong(uint8_t *buf) {
const uint8_t data[] = {
- 0x01, 0xb4, // P: push {r0} ; Obtain scratch register
- 0x02, 0x48, // ldr r0, [pc, #8] ; L2
- 0x84, 0x46, // mov ip, r0 ; high to low register
- 0x01, 0xbc, // pop {r0} ; restore scratch register
- 0xe7, 0x44, // L1: add pc, ip ; transfer control
- 0xc0, 0x46, // nop ; pad to 4-byte boundary
- 0x00, 0x00, 0x00, 0x00, // L2: .word S - (P + (L1 - P) + 4)
+ 0x78, 0x47, // P: bx pc
+ 0xfd, 0xe7, // b #-6 ; Arm recommended sequence to follow bx pc
+ 0x04, 0xc0, 0x9f, 0xe5, // ldr ip, [pc,#4] ; L2
+ 0x0c, 0xc0, 0x8f, 0xe0, // L1: add ip, pc, ip
+ 0x1c, 0xff, 0x2f, 0xe1, // bx ip
+ 0x00, 0x00, 0x00, 0x00, // L2: .word S - (P + (L1 - P) + 8)
};
uint64_t s = getARMThunkDestVA(destination);
uint64_t p = getThunkTargetSym()->getVA() & ~0x1;
memcpy(buf, data, sizeof(data));
- target->relocateNoSym(buf + 12, R_ARM_REL32, s - p - 12);
+ target->relocateNoSym(buf + 16, R_ARM_REL32, s - p - 16);
}
-void ThumbV6MPILongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__Thumbv6MPILongThunk_" + destination.getName()),
+void ThumbV4PILongThunk::addSymbols(ThunkSection &isec) {
+ addSymbol(saver().save("__Thumbv4PILongThunk_" + destination.getName()),
STT_FUNC, 1, isec);
addSymbol("$t", STT_NOTYPE, 0, isec);
- addSymbol("$d", STT_NOTYPE, 12, isec);
+ addSymbol("$a", STT_NOTYPE, 4, isec);
+ addSymbol("$d", STT_NOTYPE, 16, isec);
}
// Write MIPS LA25 thunk code to call PIC function from the non-PIC one.
}
void MipsThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__LA25Thunk_" + destination.getName()), STT_FUNC, 0,
+ addSymbol(saver().save("__LA25Thunk_" + destination.getName()), STT_FUNC, 0,
isec);
}
}
void MicroMipsThunk::addSymbols(ThunkSection &isec) {
- Defined *d = addSymbol(
- saver.save("__microLA25Thunk_" + destination.getName()), STT_FUNC, 0, isec);
+ Defined *d =
+ addSymbol(saver().save("__microLA25Thunk_" + destination.getName()),
+ STT_FUNC, 0, isec);
d->stOther |= STO_MIPS_MICROMIPS;
}
}
void MicroMipsR6Thunk::addSymbols(ThunkSection &isec) {
- Defined *d = addSymbol(
- saver.save("__microLA25Thunk_" + destination.getName()), STT_FUNC, 0, isec);
+ Defined *d =
+ addSymbol(saver().save("__microLA25Thunk_" + destination.getName()),
+ STT_FUNC, 0, isec);
d->stOther |= STO_MIPS_MICROMIPS;
}
// The stub loads an address relative to r30 (.got2+Addend). Addend is
// almost always 0x8000. The address of .got2 is different in another object
// file, so a stub cannot be shared.
- offset = gotPltVA - (in.ppc32Got2->getParent()->getVA() +
- file->ppc32Got2OutSecOff + addend);
+ offset = gotPltVA -
+ (in.ppc32Got2->getParent()->getVA() +
+ (file->ppc32Got2 ? file->ppc32Got2->outSecOff : 0) + addend);
} else {
// The stub loads an address relative to _GLOBAL_OFFSET_TABLE_ (which is
// currently the address of .got).
else
os << ".plt_pic32.";
os << destination.getName();
- addSymbol(saver.save(os.str()), STT_FUNC, 0, isec);
+ addSymbol(saver().save(os.str()), STT_FUNC, 0, isec);
}
bool PPC32PltCallStub::isCompatibleWith(const InputSection &isec,
}
void PPC32LongThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__LongThunk_" + destination.getName()), STT_FUNC, 0,
+ addSymbol(saver().save("__LongThunk_" + destination.getName()), STT_FUNC, 0,
isec);
}
}
void PPC64PltCallStub::addSymbols(ThunkSection &isec) {
- Defined *s = addSymbol(saver.save("__plt_" + destination.getName()), STT_FUNC,
- 0, isec);
+ Defined *s = addSymbol(saver().save("__plt_" + destination.getName()),
+ STT_FUNC, 0, isec);
s->needsTocRestore = true;
s->file = destination.file;
}
write32(buf + 4, 0x48000000 | (offset & 0x03fffffc)); // b <offset>
} else if (isInt<34>(offset)) {
int nextInstOffset;
- if (!config->Power10Stub) {
- uint64_t tocOffset = destination.getVA() - getPPC64TocBase();
- if (tocOffset >> 16 > 0) {
- const uint64_t addi = ADDI_R12_TO_R12_NO_DISP | (tocOffset & 0xffff);
- const uint64_t addis = ADDIS_R12_TO_R2_NO_DISP | ((tocOffset >> 16) & 0xffff);
- write32(buf + 4, addis); // addis r12, r2 , top of offset
- write32(buf + 8, addi); // addi r12, r12, bottom of offset
- nextInstOffset = 12;
- } else {
- const uint64_t addi = ADDI_R12_TO_R2_NO_DISP | (tocOffset & 0xffff);
- write32(buf + 4, addi); // addi r12, r2, offset
- nextInstOffset = 8;
- }
- } else {
- const uint64_t paddi = PADDI_R12_NO_DISP |
- (((offset >> 16) & 0x3ffff) << 32) |
- (offset & 0xffff);
- writePrefixedInstruction(buf + 4, paddi); // paddi r12, 0, func@pcrel, 1
+ uint64_t tocOffset = destination.getVA() - getPPC64TocBase();
+ if (tocOffset >> 16 > 0) {
+ const uint64_t addi = ADDI_R12_TO_R12_NO_DISP | (tocOffset & 0xffff);
+ const uint64_t addis =
+ ADDIS_R12_TO_R2_NO_DISP | ((tocOffset >> 16) & 0xffff);
+ write32(buf + 4, addis); // addis r12, r2 , top of offset
+ write32(buf + 8, addi); // addi r12, r12, bottom of offset
nextInstOffset = 12;
+ } else {
+ const uint64_t addi = ADDI_R12_TO_R2_NO_DISP | (tocOffset & 0xffff);
+ write32(buf + 4, addi); // addi r12, r2, offset
+ nextInstOffset = 8;
}
write32(buf + nextInstOffset, MTCTR_R12); // mtctr r12
write32(buf + nextInstOffset + 4, BCTR); // bctr
}
void PPC64R2SaveStub::addSymbols(ThunkSection &isec) {
- Defined *s = addSymbol(saver.save("__toc_save_" + destination.getName()),
+ Defined *s = addSymbol(saver().save("__toc_save_" + destination.getName()),
STT_FUNC, 0, isec);
s->needsTocRestore = true;
}
reportRangeError(buf, offset, 34, destination, "R12 setup stub offset");
int nextInstOffset;
- if (!config->Power10Stub) {
+ if (!config->power10Stubs) {
uint32_t off = destination.getVA(addend) - getThunkTargetSym()->getVA() - 8;
write32(buf + 0, 0x7c0802a6); // mflr r12
write32(buf + 4, 0x429f0005); // bcl 20,31,.+4
}
void PPC64R12SetupStub::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__gep_setup_" + destination.getName()), STT_FUNC, 0,
+ addSymbol(saver().save("__gep_setup_" + destination.getName()), STT_FUNC, 0,
isec);
}
int nextInstOffset = 0;
int64_t offset = destination.getGotPltVA() - getThunkTargetSym()->getVA();
- if (config->Power10Stub) {
+ if (config->power10Stubs) {
if (!isInt<34>(offset))
reportRangeError(buf, offset, 34, destination,
"PC-relative PLT stub offset");
}
void PPC64PCRelPLTStub::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__plt_pcrel_" + destination.getName()), STT_FUNC, 0,
+ addSymbol(saver().save("__plt_pcrel_" + destination.getName()), STT_FUNC, 0,
isec);
}
}
void PPC64LongBranchThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__long_branch_" + destination.getName()), STT_FUNC, 0,
+ addSymbol(saver().save("__long_branch_" + destination.getName()), STT_FUNC, 0,
isec);
}
return rel.type == R_PPC64_REL24 || rel.type == R_PPC64_REL14;
}
-void PPC64PCRelLongBranchThunk::writeTo(uint8_t *buf) {
- int64_t offset = destination.getVA() - getThunkTargetSym()->getVA();
- if (!isInt<34>(offset))
- reportRangeError(buf, offset, 34, destination,
- "PC-relative long branch stub offset");
-
- int nextInstOffset;
- if (!config->Power10Stub) {
- uint32_t off = destination.getVA(addend) - getThunkTargetSym()->getVA() - 8;
- write32(buf + 0, 0x7c0802a6); // mflr r12
- write32(buf + 4, 0x429f0005); // bcl 20,31,.+4
- write32(buf + 8, 0x7d6802a6); // mflr r11
- write32(buf + 12, 0x7d8803a6); // mtlr r12
- write32(buf + 16, 0x3d8b0000 | computeHiBits(off)); // addis r12,r11,off@ha
- write32(buf + 20, 0x398c0000 | (off & 0xffff)); // addi r12,r12,off@l
- nextInstOffset = 24;
- } else {
- uint64_t paddi = PADDI_R12_NO_DISP | (((offset >> 16) & 0x3ffff) << 32) |
- (offset & 0xffff);
- writePrefixedInstruction(buf + 0, paddi); // paddi r12, 0, func@pcrel, 1
- nextInstOffset = 8;
- }
- write32(buf + nextInstOffset, MTCTR_R12); // mtctr r12
- write32(buf + nextInstOffset + 4, BCTR); // bctr
-}
-
-void PPC64PCRelLongBranchThunk::addSymbols(ThunkSection &isec) {
- addSymbol(saver.save("__long_branch_pcrel_" + destination.getName()),
- STT_FUNC, 0, isec);
-}
-
-bool PPC64PCRelLongBranchThunk::isCompatibleWith(const InputSection &isec,
- const Relocation &rel) const {
- return rel.type == R_PPC64_REL24_NOTOC;
-}
-
Thunk::Thunk(Symbol &d, int64_t a) : destination(d), addend(a), offset(0) {}
Thunk::~Thunk() = default;
return make<AArch64ABSLongThunk>(s, a);
}
-// Creates a thunk for Thumb-ARM interworking.
-// Arm Architectures v5 and v6 do not support Thumb2 technology. This means
+// Creates a thunk for long branches or Thumb-ARM interworking.
+// Arm Architectures v4t does not support Thumb2 technology, and does not
+// support BLX or LDR Arm/Thumb state switching. This means that
+// - MOVT and MOVW instructions cannot be used.
+// - We can't rewrite BL in place to BLX. We will need thunks.
+//
+// TODO: use B for short Thumb->Arm thunks instead of LDR (this doesn't work for
+// Arm->Thumb, as in Arm state no BX PC trick; it doesn't switch state).
+static Thunk *addThunkArmv4(RelType reloc, Symbol &s, int64_t a) {
+ bool thumb_target = s.getVA(a) & 1;
+
+ switch (reloc) {
+ case R_ARM_PC24:
+ case R_ARM_PLT32:
+ case R_ARM_JUMP24:
+ case R_ARM_CALL:
+ if (config->picThunk) {
+ if (thumb_target)
+ return make<ARMV4PILongBXThunk>(s, a);
+ return make<ARMV4PILongThunk>(s, a);
+ }
+ if (thumb_target)
+ return make<ARMV4ABSLongBXThunk>(s, a);
+ return make<ARMV5LongLdrPcThunk>(s, a);
+ case R_ARM_THM_CALL:
+ if (config->picThunk) {
+ if (thumb_target)
+ return make<ThumbV4PILongThunk>(s, a);
+ return make<ThumbV4PILongBXThunk>(s, a);
+ }
+ if (thumb_target)
+ return make<ThumbV4ABSLongThunk>(s, a);
+ return make<ThumbV4ABSLongBXThunk>(s, a);
+ }
+ fatal("relocation " + toString(reloc) + " to " + toString(s) +
+ " not supported for Armv4 or Armv4T target");
+}
+
+// Creates a thunk for Thumb-ARM interworking compatible with Armv5 and Armv6.
+// Arm Architectures v5 and v6 do not support Thumb2 technology. This means that
// - MOVT and MOVW instructions cannot be used
// - Only Thumb relocation that can generate a Thunk is a BL, this can always
// be transformed into a BLX
-static Thunk *addThunkPreArmv7(RelType reloc, Symbol &s, int64_t a) {
+static Thunk *addThunkArmv5v6(RelType reloc, Symbol &s, int64_t a) {
switch (reloc) {
case R_ARM_PC24:
case R_ARM_PLT32:
case R_ARM_CALL:
case R_ARM_THM_CALL:
if (config->picThunk)
- return make<ARMV5PILongThunk>(s, a);
- return make<ARMV5ABSLongThunk>(s, a);
+ return make<ARMV4PILongBXThunk>(s, a);
+ return make<ARMV5LongLdrPcThunk>(s, a);
}
fatal("relocation " + toString(reloc) + " to " + toString(s) +
" not supported for Armv5 or Armv6 targets");
// of the input objects. InputFiles.cpp contains the mapping from ARM
// architecture to flag.
if (!config->armHasMovtMovw) {
- if (!config->armJ1J2BranchEncoding)
- return addThunkPreArmv7(reloc, s, a);
- return addThunkV6M(reloc, s, a);
+ if (config->armJ1J2BranchEncoding)
+ return addThunkV6M(reloc, s, a);
+ if (config->armHasBlx)
+ return addThunkArmv5v6(reloc, s, a);
+ return addThunkArmv4(reloc, s, a);
}
switch (reloc) {
return make<PPC64R2SaveStub>(s, a);
if (type == R_PPC64_REL24_NOTOC)
- return (s.stOther >> 5) > 1
- ? (Thunk *)make<PPC64R12SetupStub>(s)
- : (Thunk *)make<PPC64PCRelLongBranchThunk>(s, a);
+ return make<PPC64R12SetupStub>(s);
if (config->picThunk)
return make<PPC64PILongBranchThunk>(s, a);
#include "llvm/ADT/SmallVector.h"
#include "Relocations.h"
-namespace lld {
-namespace elf {
+namespace lld::elf {
class Defined;
class InputFile;
class Symbol;
return (toCompute + 0x8000) >> 16;
}
-} // namespace elf
-} // namespace lld
+} // namespace lld::elf
#endif
void relocateOne(uint8_t *loc, const Reloc &, uint64_t va,
uint64_t pc) const override;
- void writeStub(uint8_t *buf, const Symbol &) const override;
+ void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
void writeStubHelperHeader(uint8_t *buf) const override;
- void writeStubHelperEntry(uint8_t *buf, const DylibSymbol &,
+ void writeStubHelperEntry(uint8_t *buf, const Symbol &,
uint64_t entryAddr) const override;
+ void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
+ uint64_t stubOffset, uint64_t selrefsVA,
+ uint64_t selectorIndex, uint64_t gotAddr,
+ uint64_t msgSendIndex) const override;
+
void relaxGotLoad(uint8_t *loc, uint8_t type) const override;
- const RelocAttrs &getRelocAttrs(uint8_t type) const override;
uint64_t getPageSize() const override { return 4 * 1024; }
-};
+ void handleDtraceReloc(const Symbol *sym, const Reloc &r,
+ uint8_t *loc) const override;
+};
} // namespace
-const RelocAttrs &ARM::getRelocAttrs(uint8_t type) const {
- static const std::array<RelocAttrs, 10> relocAttrsArray{{
+static constexpr std::array<RelocAttrs, 10> relocAttrsArray{{
#define B(x) RelocAttrBits::x
- {"VANILLA", /* FIXME populate this */ B(_0)},
- {"PAIR", /* FIXME populate this */ B(_0)},
- {"SECTDIFF", /* FIXME populate this */ B(_0)},
- {"LOCAL_SECTDIFF", /* FIXME populate this */ B(_0)},
- {"PB_LA_PTR", /* FIXME populate this */ B(_0)},
- {"BR24", B(PCREL) | B(LOCAL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
- {"BR22", B(PCREL) | B(LOCAL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
- {"32BIT_BRANCH", /* FIXME populate this */ B(_0)},
- {"HALF", /* FIXME populate this */ B(_0)},
- {"HALF_SECTDIFF", /* FIXME populate this */ B(_0)},
+ {"VANILLA", /* FIXME populate this */ B(_0)},
+ {"PAIR", /* FIXME populate this */ B(_0)},
+ {"SECTDIFF", /* FIXME populate this */ B(_0)},
+ {"LOCAL_SECTDIFF", /* FIXME populate this */ B(_0)},
+ {"PB_LA_PTR", /* FIXME populate this */ B(_0)},
+ {"BR24", B(PCREL) | B(LOCAL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
+ {"BR22", B(PCREL) | B(LOCAL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
+ {"32BIT_BRANCH", /* FIXME populate this */ B(_0)},
+ {"HALF", /* FIXME populate this */ B(_0)},
+ {"HALF_SECTDIFF", /* FIXME populate this */ B(_0)},
#undef B
- }};
- assert(type < relocAttrsArray.size() && "invalid relocation type");
- if (type >= relocAttrsArray.size())
- return invalidRelocAttrs;
- return relocAttrsArray[type];
-}
+}};
int64_t ARM::getEmbeddedAddend(MemoryBufferRef mb, uint64_t offset,
relocation_info rel) const {
return;
} else if (isBlx && !defined->thumb) {
Bitfield::set<Cond>(base, 0xe); // unconditional BL
- Bitfield::set<BitfieldFlag<24>>(base, 1);
+ Bitfield::set<BitfieldFlag<24>>(base, true);
isBlx = false;
}
} else {
}
}
-void ARM::writeStub(uint8_t *buf, const Symbol &sym) const {
+void ARM::writeStub(uint8_t *buf, const Symbol &sym, uint64_t) const {
fatal("TODO: implement this");
}
fatal("TODO: implement this");
}
-void ARM::writeStubHelperEntry(uint8_t *buf, const DylibSymbol &sym,
+void ARM::writeStubHelperEntry(uint8_t *buf, const Symbol &sym,
uint64_t entryAddr) const {
fatal("TODO: implement this");
}
+void ARM::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
+ uint64_t stubOffset, uint64_t selrefsVA,
+ uint64_t selectorIndex, uint64_t gotAddr,
+ uint64_t msgSendIndex) const {
+ fatal("TODO: implement this");
+}
+
void ARM::relaxGotLoad(uint8_t *loc, uint8_t type) const {
fatal("TODO: implement this");
}
stubSize = 0 /* FIXME */;
stubHelperHeaderSize = 0 /* FIXME */;
stubHelperEntrySize = 0 /* FIXME */;
+
+ relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
}
TargetInfo *macho::createARMTargetInfo(uint32_t cpuSubtype) {
static ARM t(cpuSubtype);
return &t;
}
+
+void ARM::handleDtraceReloc(const Symbol *sym, const Reloc &r,
+ uint8_t *loc) const {
+ if (config->outputType == MH_OBJECT)
+ return;
+
+ switch (r.type) {
+ case ARM_RELOC_BR24:
+ if (sym->getName().startswith("___dtrace_probe")) {
+ // change call site to a NOP
+ write32le(loc, 0xE1A00000);
+ } else if (sym->getName().startswith("___dtrace_isenabled")) {
+ // change call site to 'eor r0, r0, r0'
+ write32le(loc, 0xE0200000);
+ } else {
+ error("Unrecognized dtrace symbol prefix: " + toString(*sym));
+ }
+ break;
+ case ARM_THUMB_RELOC_BR22:
+ if (sym->getName().startswith("___dtrace_probe")) {
+ // change 32-bit blx call site to two thumb NOPs
+ write32le(loc, 0x46C046C0);
+ } else if (sym->getName().startswith("___dtrace_isenabled")) {
+ // change 32-bit blx call site to 'nop', 'eor r0, r0'
+ write32le(loc, 0x46C04040);
+ } else {
+ error("Unrecognized dtrace symbol prefix: " + toString(*sym));
+ }
+ break;
+ default:
+ llvm_unreachable("Unsupported dtrace relocation type for ARM");
+ }
+}
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
+#include "mach-o/compact_unwind_encoding.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/Endian.h"
+#include "llvm/Support/LEB128.h"
#include "llvm/Support/MathExtras.h"
using namespace llvm;
struct ARM64 : ARM64Common {
ARM64();
- void writeStub(uint8_t *buf, const Symbol &) const override;
+ void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
void writeStubHelperHeader(uint8_t *buf) const override;
- void writeStubHelperEntry(uint8_t *buf, const DylibSymbol &,
+ void writeStubHelperEntry(uint8_t *buf, const Symbol &,
uint64_t entryAddr) const override;
- const RelocAttrs &getRelocAttrs(uint8_t type) const override;
+
+ void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
+ uint64_t stubOffset, uint64_t selrefsVA,
+ uint64_t selectorIndex, uint64_t gotAddr,
+ uint64_t msgSendIndex) const override;
void populateThunk(InputSection *thunk, Symbol *funcSym) override;
+ void applyOptimizationHints(uint8_t *, const ObjFile &) const override;
};
} // namespace
// absolute version of this relocation. The semantics of the absolute relocation
// are weird -- it results in the value of the GOT slot being written, instead
// of the address. Let's not support it unless we find a real-world use case.
-
-const RelocAttrs &ARM64::getRelocAttrs(uint8_t type) const {
- static const std::array<RelocAttrs, 11> relocAttrsArray{{
+static constexpr std::array<RelocAttrs, 11> relocAttrsArray{{
#define B(x) RelocAttrBits::x
- {"UNSIGNED",
- B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4) | B(BYTE8)},
- {"SUBTRACTOR", B(SUBTRAHEND) | B(EXTERN) | B(BYTE4) | B(BYTE8)},
- {"BRANCH26", B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
- {"PAGE21", B(PCREL) | B(EXTERN) | B(BYTE4)},
- {"PAGEOFF12", B(ABSOLUTE) | B(EXTERN) | B(BYTE4)},
- {"GOT_LOAD_PAGE21", B(PCREL) | B(EXTERN) | B(GOT) | B(BYTE4)},
- {"GOT_LOAD_PAGEOFF12",
- B(ABSOLUTE) | B(EXTERN) | B(GOT) | B(LOAD) | B(BYTE4)},
- {"POINTER_TO_GOT", B(PCREL) | B(EXTERN) | B(GOT) | B(POINTER) | B(BYTE4)},
- {"TLVP_LOAD_PAGE21", B(PCREL) | B(EXTERN) | B(TLV) | B(BYTE4)},
- {"TLVP_LOAD_PAGEOFF12",
- B(ABSOLUTE) | B(EXTERN) | B(TLV) | B(LOAD) | B(BYTE4)},
- {"ADDEND", B(ADDEND)},
+ {"UNSIGNED",
+ B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4) | B(BYTE8)},
+ {"SUBTRACTOR", B(SUBTRAHEND) | B(EXTERN) | B(BYTE4) | B(BYTE8)},
+ {"BRANCH26", B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
+ {"PAGE21", B(PCREL) | B(EXTERN) | B(BYTE4)},
+ {"PAGEOFF12", B(ABSOLUTE) | B(EXTERN) | B(BYTE4)},
+ {"GOT_LOAD_PAGE21", B(PCREL) | B(EXTERN) | B(GOT) | B(BYTE4)},
+ {"GOT_LOAD_PAGEOFF12",
+ B(ABSOLUTE) | B(EXTERN) | B(GOT) | B(LOAD) | B(BYTE4)},
+ {"POINTER_TO_GOT", B(PCREL) | B(EXTERN) | B(GOT) | B(POINTER) | B(BYTE4)},
+ {"TLVP_LOAD_PAGE21", B(PCREL) | B(EXTERN) | B(TLV) | B(BYTE4)},
+ {"TLVP_LOAD_PAGEOFF12",
+ B(ABSOLUTE) | B(EXTERN) | B(TLV) | B(LOAD) | B(BYTE4)},
+ {"ADDEND", B(ADDEND)},
#undef B
- }};
- assert(type < relocAttrsArray.size() && "invalid relocation type");
- if (type >= relocAttrsArray.size())
- return invalidRelocAttrs;
- return relocAttrsArray[type];
-}
+}};
static constexpr uint32_t stubCode[] = {
0x90000010, // 00: adrp x16, __la_symbol_ptr@page
0xd61f0200, // 08: br x16
};
-void ARM64::writeStub(uint8_t *buf8, const Symbol &sym) const {
- ::writeStub<LP64>(buf8, stubCode, sym);
+void ARM64::writeStub(uint8_t *buf8, const Symbol &sym,
+ uint64_t pointerVA) const {
+ ::writeStub(buf8, stubCode, sym, pointerVA);
}
static constexpr uint32_t stubHelperHeaderCode[] = {
0x00000000, // 08: l0: .long 0
};
-void ARM64::writeStubHelperEntry(uint8_t *buf8, const DylibSymbol &sym,
+void ARM64::writeStubHelperEntry(uint8_t *buf8, const Symbol &sym,
uint64_t entryVA) const {
::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA);
}
+static constexpr uint32_t objcStubsFastCode[] = {
+ 0x90000001, // adrp x1, __objc_selrefs@page
+ 0xf9400021, // ldr x1, [x1, @selector("foo")@pageoff]
+ 0x90000010, // adrp x16, _got@page
+ 0xf9400210, // ldr x16, [x16, _objc_msgSend@pageoff]
+ 0xd61f0200, // br x16
+ 0xd4200020, // brk #0x1
+ 0xd4200020, // brk #0x1
+ 0xd4200020, // brk #0x1
+};
+
+void ARM64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
+ uint64_t stubOffset, uint64_t selrefsVA,
+ uint64_t selectorIndex, uint64_t gotAddr,
+ uint64_t msgSendIndex) const {
+ ::writeObjCMsgSendStub<LP64>(buf, objcStubsFastCode, sym, stubsAddr,
+ stubOffset, selrefsVA, selectorIndex, gotAddr,
+ msgSendIndex);
+}
+
// A thunk is the relaxed variation of stubCode. We don't need the
// extra indirection through a lazy pointer because the target address
// is known at link time.
stubSize = sizeof(stubCode);
thunkSize = sizeof(thunkCode);
- branchRange = maxIntN(28) - thunkSize;
+
+ objcStubsFastSize = sizeof(objcStubsFastCode);
+ objcStubsAlignment = 32;
+
+ // Branch immediate is two's complement 26 bits, which is implicitly
+ // multiplied by 4 (since all functions are 4-aligned: The branch range
+ // is -4*(2**(26-1))..4*(2**(26-1) - 1).
+ backwardBranchRange = 128 * 1024 * 1024;
+ forwardBranchRange = backwardBranchRange - 4;
+
+ modeDwarfEncoding = UNWIND_ARM64_MODE_DWARF;
+ subtractorRelocType = ARM64_RELOC_SUBTRACTOR;
+ unsignedRelocType = ARM64_RELOC_UNSIGNED;
+
stubHelperHeaderSize = sizeof(stubHelperHeaderCode);
stubHelperEntrySize = sizeof(stubHelperEntryCode);
+
+ relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
+}
+
+namespace {
+struct Adrp {
+ uint32_t destRegister;
+ int64_t addend;
+};
+
+struct Add {
+ uint8_t destRegister;
+ uint8_t srcRegister;
+ uint32_t addend;
+};
+
+enum ExtendType { ZeroExtend = 1, Sign64 = 2, Sign32 = 3 };
+
+struct Ldr {
+ uint8_t destRegister;
+ uint8_t baseRegister;
+ uint8_t p2Size;
+ bool isFloat;
+ ExtendType extendType;
+ int64_t offset;
+};
+} // namespace
+
+static bool parseAdrp(uint32_t insn, Adrp &adrp) {
+ if ((insn & 0x9f000000) != 0x90000000)
+ return false;
+ adrp.destRegister = insn & 0x1f;
+ uint64_t immHi = (insn >> 5) & 0x7ffff;
+ uint64_t immLo = (insn >> 29) & 0x3;
+ adrp.addend = SignExtend64<21>(immLo | (immHi << 2)) * 4096;
+ return true;
+}
+
+static bool parseAdd(uint32_t insn, Add &add) {
+ if ((insn & 0xffc00000) != 0x91000000)
+ return false;
+ add.destRegister = insn & 0x1f;
+ add.srcRegister = (insn >> 5) & 0x1f;
+ add.addend = (insn >> 10) & 0xfff;
+ return true;
+}
+
+static bool parseLdr(uint32_t insn, Ldr &ldr) {
+ ldr.destRegister = insn & 0x1f;
+ ldr.baseRegister = (insn >> 5) & 0x1f;
+ uint8_t size = insn >> 30;
+ uint8_t opc = (insn >> 22) & 3;
+
+ if ((insn & 0x3fc00000) == 0x39400000) {
+ // LDR (immediate), LDRB (immediate), LDRH (immediate)
+ ldr.p2Size = size;
+ ldr.extendType = ZeroExtend;
+ ldr.isFloat = false;
+ } else if ((insn & 0x3f800000) == 0x39800000) {
+ // LDRSB (immediate), LDRSH (immediate), LDRSW (immediate)
+ ldr.p2Size = size;
+ ldr.extendType = static_cast<ExtendType>(opc);
+ ldr.isFloat = false;
+ } else if ((insn & 0x3f400000) == 0x3d400000) {
+ // LDR (immediate, SIMD&FP)
+ ldr.extendType = ZeroExtend;
+ ldr.isFloat = true;
+ if (opc == 1)
+ ldr.p2Size = size;
+ else if (size == 0 && opc == 3)
+ ldr.p2Size = 4;
+ else
+ return false;
+ } else {
+ return false;
+ }
+ ldr.offset = ((insn >> 10) & 0xfff) << ldr.p2Size;
+ return true;
+}
+
+static bool isValidAdrOffset(int32_t delta) { return isInt<21>(delta); }
+
+static void writeAdr(void *loc, uint32_t dest, int32_t delta) {
+ assert(isValidAdrOffset(delta));
+ uint32_t opcode = 0x10000000;
+ uint32_t immHi = (delta & 0x001ffffc) << 3;
+ uint32_t immLo = (delta & 0x00000003) << 29;
+ write32le(loc, opcode | immHi | immLo | dest);
+}
+
+static void writeNop(void *loc) { write32le(loc, 0xd503201f); }
+
+static bool isLiteralLdrEligible(const Ldr &ldr) {
+ return ldr.p2Size > 1 && isShiftedInt<19, 2>(ldr.offset);
+}
+
+static void writeLiteralLdr(void *loc, const Ldr &ldr) {
+ assert(isLiteralLdrEligible(ldr));
+ uint32_t imm19 = (ldr.offset / 4 & maskTrailingOnes<uint32_t>(19)) << 5;
+ uint32_t opcode;
+ switch (ldr.p2Size) {
+ case 2:
+ if (ldr.isFloat)
+ opcode = 0x1c000000;
+ else
+ opcode = ldr.extendType == Sign64 ? 0x98000000 : 0x18000000;
+ break;
+ case 3:
+ opcode = ldr.isFloat ? 0x5c000000 : 0x58000000;
+ break;
+ case 4:
+ opcode = 0x9c000000;
+ break;
+ default:
+ llvm_unreachable("Invalid literal ldr size");
+ }
+ write32le(loc, opcode | imm19 | ldr.destRegister);
+}
+
+static bool isImmediateLdrEligible(const Ldr &ldr) {
+ // Note: We deviate from ld64's behavior, which converts to immediate loads
+ // only if ldr.offset < 4096, even though the offset is divided by the load's
+ // size in the 12-bit immediate operand. Only the unsigned offset variant is
+ // supported.
+
+ uint32_t size = 1 << ldr.p2Size;
+ return ldr.offset >= 0 && (ldr.offset % size) == 0 &&
+ isUInt<12>(ldr.offset >> ldr.p2Size);
+}
+
+static void writeImmediateLdr(void *loc, const Ldr &ldr) {
+ assert(isImmediateLdrEligible(ldr));
+ uint32_t opcode = 0x39000000;
+ if (ldr.isFloat) {
+ opcode |= 0x04000000;
+ assert(ldr.extendType == ZeroExtend);
+ }
+ opcode |= ldr.destRegister;
+ opcode |= ldr.baseRegister << 5;
+ uint8_t size, opc;
+ if (ldr.p2Size == 4) {
+ size = 0;
+ opc = 3;
+ } else {
+ opc = ldr.extendType;
+ size = ldr.p2Size;
+ }
+ uint32_t immBits = ldr.offset >> ldr.p2Size;
+ write32le(loc, opcode | (immBits << 10) | (opc << 22) | (size << 30));
+}
+
+// Transforms a pair of adrp+add instructions into an adr instruction if the
+// target is within the +/- 1 MiB range allowed by the adr's 21 bit signed
+// immediate offset.
+//
+// adrp xN, _foo@PAGE
+// add xM, xN, _foo@PAGEOFF
+// ->
+// adr xM, _foo
+// nop
+static void applyAdrpAdd(uint8_t *buf, const ConcatInputSection *isec,
+ uint64_t offset1, uint64_t offset2) {
+ uint32_t ins1 = read32le(buf + offset1);
+ uint32_t ins2 = read32le(buf + offset2);
+ Adrp adrp;
+ Add add;
+ if (!parseAdrp(ins1, adrp) || !parseAdd(ins2, add))
+ return;
+ if (adrp.destRegister != add.srcRegister)
+ return;
+
+ uint64_t addr1 = isec->getVA() + offset1;
+ uint64_t referent = pageBits(addr1) + adrp.addend + add.addend;
+ int64_t delta = referent - addr1;
+ if (!isValidAdrOffset(delta))
+ return;
+
+ writeAdr(buf + offset1, add.destRegister, delta);
+ writeNop(buf + offset2);
+}
+
+// Transforms two adrp instructions into a single adrp if their referent
+// addresses are located on the same 4096 byte page.
+//
+// adrp xN, _foo@PAGE
+// adrp xN, _bar@PAGE
+// ->
+// adrp xN, _foo@PAGE
+// nop
+static void applyAdrpAdrp(uint8_t *buf, const ConcatInputSection *isec,
+ uint64_t offset1, uint64_t offset2) {
+ uint32_t ins1 = read32le(buf + offset1);
+ uint32_t ins2 = read32le(buf + offset2);
+ Adrp adrp1, adrp2;
+ if (!parseAdrp(ins1, adrp1) || !parseAdrp(ins2, adrp2))
+ return;
+ if (adrp1.destRegister != adrp2.destRegister)
+ return;
+
+ uint64_t page1 = pageBits(offset1 + isec->getVA()) + adrp1.addend;
+ uint64_t page2 = pageBits(offset2 + isec->getVA()) + adrp2.addend;
+ if (page1 != page2)
+ return;
+
+ writeNop(buf + offset2);
+}
+
+// Transforms a pair of adrp+ldr (immediate) instructions into an ldr (literal)
+// load from a PC-relative address if it is 4-byte aligned and within +/- 1 MiB,
+// as ldr can encode a signed 19-bit offset that gets multiplied by 4.
+//
+// adrp xN, _foo@PAGE
+// ldr xM, [xN, _foo@PAGEOFF]
+// ->
+// nop
+// ldr xM, _foo
+static void applyAdrpLdr(uint8_t *buf, const ConcatInputSection *isec,
+ uint64_t offset1, uint64_t offset2) {
+ uint32_t ins1 = read32le(buf + offset1);
+ uint32_t ins2 = read32le(buf + offset2);
+ Adrp adrp;
+ Ldr ldr;
+ if (!parseAdrp(ins1, adrp) || !parseLdr(ins2, ldr))
+ return;
+ if (adrp.destRegister != ldr.baseRegister)
+ return;
+
+ uint64_t addr1 = isec->getVA() + offset1;
+ uint64_t addr2 = isec->getVA() + offset2;
+ uint64_t referent = pageBits(addr1) + adrp.addend + ldr.offset;
+ ldr.offset = referent - addr2;
+ if (!isLiteralLdrEligible(ldr))
+ return;
+
+ writeNop(buf + offset1);
+ writeLiteralLdr(buf + offset2, ldr);
+}
+
+// GOT loads are emitted by the compiler as a pair of adrp and ldr instructions,
+// but they may be changed to adrp+add by relaxGotLoad(). This hint performs
+// the AdrpLdr or AdrpAdd transformation depending on whether it was relaxed.
+static void applyAdrpLdrGot(uint8_t *buf, const ConcatInputSection *isec,
+ uint64_t offset1, uint64_t offset2) {
+ uint32_t ins2 = read32le(buf + offset2);
+ Add add;
+ Ldr ldr;
+ if (parseAdd(ins2, add))
+ applyAdrpAdd(buf, isec, offset1, offset2);
+ else if (parseLdr(ins2, ldr))
+ applyAdrpLdr(buf, isec, offset1, offset2);
+}
+
+// Optimizes an adrp+add+ldr sequence used for loading from a local symbol's
+// address by loading directly if it's close enough, or to an adrp(p)+ldr
+// sequence if it's not.
+//
+// adrp x0, _foo@PAGE
+// add x1, x0, _foo@PAGEOFF
+// ldr x2, [x1, #off]
+static void applyAdrpAddLdr(uint8_t *buf, const ConcatInputSection *isec,
+ uint64_t offset1, uint64_t offset2,
+ uint64_t offset3) {
+ uint32_t ins1 = read32le(buf + offset1);
+ Adrp adrp;
+ if (!parseAdrp(ins1, adrp))
+ return;
+ uint32_t ins2 = read32le(buf + offset2);
+ Add add;
+ if (!parseAdd(ins2, add))
+ return;
+ uint32_t ins3 = read32le(buf + offset3);
+ Ldr ldr;
+ if (!parseLdr(ins3, ldr))
+ return;
+ if (adrp.destRegister != add.srcRegister)
+ return;
+ if (add.destRegister != ldr.baseRegister)
+ return;
+
+ // Load from the target address directly.
+ // nop
+ // nop
+ // ldr x2, [_foo + #off]
+ uint64_t addr1 = isec->getVA() + offset1;
+ uint64_t addr3 = isec->getVA() + offset3;
+ uint64_t referent = pageBits(addr1) + adrp.addend + add.addend;
+ Ldr literalLdr = ldr;
+ literalLdr.offset += referent - addr3;
+ if (isLiteralLdrEligible(literalLdr)) {
+ writeNop(buf + offset1);
+ writeNop(buf + offset2);
+ writeLiteralLdr(buf + offset3, literalLdr);
+ return;
+ }
+
+ // Load the target address into a register and load from there indirectly.
+ // adr x1, _foo
+ // nop
+ // ldr x2, [x1, #off]
+ int64_t adrOffset = referent - addr1;
+ if (isValidAdrOffset(adrOffset)) {
+ writeAdr(buf + offset1, ldr.baseRegister, adrOffset);
+ // Note: ld64 moves the offset into the adr instruction for AdrpAddLdr, but
+ // not for AdrpLdrGotLdr. Its effect is the same either way.
+ writeNop(buf + offset2);
+ return;
+ }
+
+ // Move the target's page offset into the ldr's immediate offset.
+ // adrp x0, _foo@PAGE
+ // nop
+ // ldr x2, [x0, _foo@PAGEOFF + #off]
+ Ldr immediateLdr = ldr;
+ immediateLdr.baseRegister = adrp.destRegister;
+ immediateLdr.offset += add.addend;
+ if (isImmediateLdrEligible(immediateLdr)) {
+ writeNop(buf + offset2);
+ writeImmediateLdr(buf + offset3, immediateLdr);
+ return;
+ }
+}
+
+// Relaxes a GOT-indirect load.
+// If the referenced symbol is external and its GOT entry is within +/- 1 MiB,
+// the GOT entry can be loaded with a single literal ldr instruction.
+// If the referenced symbol is local and thus has been relaxed to adrp+add+ldr,
+// we perform the AdrpAddLdr transformation.
+static void applyAdrpLdrGotLdr(uint8_t *buf, const ConcatInputSection *isec,
+ uint64_t offset1, uint64_t offset2,
+ uint64_t offset3) {
+ uint32_t ins2 = read32le(buf + offset2);
+ Add add;
+ Ldr ldr2;
+
+ if (parseAdd(ins2, add)) {
+ applyAdrpAddLdr(buf, isec, offset1, offset2, offset3);
+ } else if (parseLdr(ins2, ldr2)) {
+ // adrp x1, _foo@GOTPAGE
+ // ldr x2, [x1, _foo@GOTPAGEOFF]
+ // ldr x3, [x2, #off]
+
+ uint32_t ins1 = read32le(buf + offset1);
+ Adrp adrp;
+ if (!parseAdrp(ins1, adrp))
+ return;
+ uint32_t ins3 = read32le(buf + offset3);
+ Ldr ldr3;
+ if (!parseLdr(ins3, ldr3))
+ return;
+
+ if (ldr2.baseRegister != adrp.destRegister)
+ return;
+ if (ldr3.baseRegister != ldr2.destRegister)
+ return;
+ // Loads from the GOT must be pointer sized.
+ if (ldr2.p2Size != 3 || ldr2.isFloat)
+ return;
+
+ uint64_t addr1 = isec->getVA() + offset1;
+ uint64_t addr2 = isec->getVA() + offset2;
+ uint64_t referent = pageBits(addr1) + adrp.addend + ldr2.offset;
+ // Load the GOT entry's address directly.
+ // nop
+ // ldr x2, _foo@GOTPAGE + _foo@GOTPAGEOFF
+ // ldr x3, [x2, #off]
+ Ldr literalLdr = ldr2;
+ literalLdr.offset = referent - addr2;
+ if (isLiteralLdrEligible(literalLdr)) {
+ writeNop(buf + offset1);
+ writeLiteralLdr(buf + offset2, literalLdr);
+ }
+ }
+}
+
+static uint64_t readValue(const uint8_t *&ptr, const uint8_t *end) {
+ unsigned int n = 0;
+ uint64_t value = decodeULEB128(ptr, &n, end);
+ ptr += n;
+ return value;
+}
+
+template <typename Callback>
+static void forEachHint(ArrayRef<uint8_t> data, Callback callback) {
+ std::array<uint64_t, 3> args;
+
+ for (const uint8_t *p = data.begin(), *end = data.end(); p < end;) {
+ uint64_t type = readValue(p, end);
+ if (type == 0)
+ break;
+
+ uint64_t argCount = readValue(p, end);
+ // All known LOH types as of 2022-09 have 3 or fewer arguments; skip others.
+ if (argCount > 3) {
+ for (unsigned i = 0; i < argCount; ++i)
+ readValue(p, end);
+ continue;
+ }
+
+ for (unsigned i = 0; i < argCount; ++i)
+ args[i] = readValue(p, end);
+ callback(type, ArrayRef<uint64_t>(args.data(), argCount));
+ }
+}
+
+// On RISC architectures like arm64, materializing a memory address generally
+// takes multiple instructions. If the referenced symbol is located close enough
+// in memory, fewer instructions are needed.
+//
+// Linker optimization hints record where addresses are computed. After
+// addresses have been assigned, if possible, we change them to a shorter
+// sequence of instructions. The size of the binary is not modified; the
+// eliminated instructions are replaced with NOPs. This still leads to faster
+// code as the CPU can skip over NOPs quickly.
+//
+// LOHs are specified by the LC_LINKER_OPTIMIZATION_HINTS load command, which
+// points to a sequence of ULEB128-encoded numbers. Each entry specifies a
+// transformation kind, and 2 or 3 addresses where the instructions are located.
+void ARM64::applyOptimizationHints(uint8_t *outBuf, const ObjFile &obj) const {
+ ArrayRef<uint8_t> data = obj.getOptimizationHints();
+ if (data.empty())
+ return;
+
+ const ConcatInputSection *section = nullptr;
+ uint64_t sectionAddr = 0;
+ uint8_t *buf = nullptr;
+
+ auto findSection = [&](uint64_t addr) {
+ if (section && addr >= sectionAddr &&
+ addr < sectionAddr + section->getSize())
+ return true;
+
+ auto secIt = std::prev(llvm::upper_bound(
+ obj.sections, addr,
+ [](uint64_t off, const Section *sec) { return off < sec->addr; }));
+ const Section *sec = *secIt;
+
+ auto subsecIt = std::prev(llvm::upper_bound(
+ sec->subsections, addr - sec->addr,
+ [](uint64_t off, Subsection subsec) { return off < subsec.offset; }));
+ const Subsection &subsec = *subsecIt;
+ const ConcatInputSection *isec =
+ dyn_cast_or_null<ConcatInputSection>(subsec.isec);
+ if (!isec || isec->shouldOmitFromOutput())
+ return false;
+
+ section = isec;
+ sectionAddr = subsec.offset + sec->addr;
+ buf = outBuf + section->outSecOff + section->parent->fileOff;
+ return true;
+ };
+
+ auto isValidOffset = [&](uint64_t offset) {
+ if (offset < sectionAddr || offset >= sectionAddr + section->getSize()) {
+ error(toString(&obj) +
+ ": linker optimization hint spans multiple sections");
+ return false;
+ }
+ return true;
+ };
+
+ bool hasAdrpAdrp = false;
+ forEachHint(data, [&](uint64_t kind, ArrayRef<uint64_t> args) {
+ if (kind == LOH_ARM64_ADRP_ADRP) {
+ hasAdrpAdrp = true;
+ return;
+ }
+
+ if (!findSection(args[0]))
+ return;
+ switch (kind) {
+ case LOH_ARM64_ADRP_ADD:
+ if (isValidOffset(args[1]))
+ applyAdrpAdd(buf, section, args[0] - sectionAddr,
+ args[1] - sectionAddr);
+ break;
+ case LOH_ARM64_ADRP_LDR:
+ if (isValidOffset(args[1]))
+ applyAdrpLdr(buf, section, args[0] - sectionAddr,
+ args[1] - sectionAddr);
+ break;
+ case LOH_ARM64_ADRP_LDR_GOT:
+ if (isValidOffset(args[1]))
+ applyAdrpLdrGot(buf, section, args[0] - sectionAddr,
+ args[1] - sectionAddr);
+ break;
+ case LOH_ARM64_ADRP_ADD_LDR:
+ if (isValidOffset(args[1]) && isValidOffset(args[2]))
+ applyAdrpAddLdr(buf, section, args[0] - sectionAddr,
+ args[1] - sectionAddr, args[2] - sectionAddr);
+ break;
+ case LOH_ARM64_ADRP_LDR_GOT_LDR:
+ if (isValidOffset(args[1]) && isValidOffset(args[2]))
+ applyAdrpLdrGotLdr(buf, section, args[0] - sectionAddr,
+ args[1] - sectionAddr, args[2] - sectionAddr);
+ break;
+ case LOH_ARM64_ADRP_ADD_STR:
+ case LOH_ARM64_ADRP_LDR_GOT_STR:
+ // TODO: Implement these
+ break;
+ }
+ });
+
+ if (!hasAdrpAdrp)
+ return;
+
+ // AdrpAdrp optimization hints are performed in a second pass because they
+ // might interfere with other transformations. For instance, consider the
+ // following input:
+ //
+ // adrp x0, _foo@PAGE
+ // add x1, x0, _foo@PAGEOFF
+ // adrp x0, _bar@PAGE
+ // add x2, x0, _bar@PAGEOFF
+ //
+ // If we perform the AdrpAdrp relaxation first, we get:
+ //
+ // adrp x0, _foo@PAGE
+ // add x1, x0, _foo@PAGEOFF
+ // nop
+ // add x2, x0, _bar@PAGEOFF
+ //
+ // If we then apply AdrpAdd to the first two instructions, the add will have a
+ // garbage value in x0:
+ //
+ // adr x1, _foo
+ // nop
+ // nop
+ // add x2, x0, _bar@PAGEOFF
+ forEachHint(data, [&](uint64_t kind, ArrayRef<uint64_t> args) {
+ if (kind != LOH_ARM64_ADRP_ADRP)
+ return;
+ if (!findSection(args[0]))
+ return;
+ if (isValidOffset(args[1]))
+ applyAdrpAdrp(buf, section, args[0] - sectionAddr, args[1] - sectionAddr);
+ });
}
TargetInfo *macho::createARM64TargetInfo() {
}
}
+static void writeValue(uint8_t *loc, const Reloc &r, uint64_t value) {
+ switch (r.length) {
+ case 2:
+ checkInt(loc, r, value, 32);
+ write32le(loc, value);
+ break;
+ case 3:
+ write64le(loc, value);
+ break;
+ default:
+ llvm_unreachable("invalid r_length");
+ }
+}
+
// For instruction relocations (load, store, add), the base
// instruction is pre-populated in the text section. A pre-populated
// instruction has opcode & register-operand bits set, with immediate
// operands zeroed. We read it from text, OR-in the immediate
// operands, then write-back the completed instruction.
-
void ARM64Common::relocateOne(uint8_t *loc, const Reloc &r, uint64_t value,
uint64_t pc) const {
+ auto loc32 = reinterpret_cast<uint32_t *>(loc);
uint32_t base = ((r.length == 2) ? read32le(loc) : 0);
switch (r.type) {
case ARM64_RELOC_BRANCH26:
- value = encodeBranch26(r, base, value - pc);
+ encodeBranch26(loc32, r, base, value - pc);
break;
case ARM64_RELOC_SUBTRACTOR:
case ARM64_RELOC_UNSIGNED:
- if (r.length == 2)
- checkInt(r, value, 32);
+ writeValue(loc, r, value);
break;
case ARM64_RELOC_POINTER_TO_GOT:
if (r.pcrel)
value -= pc;
- checkInt(r, value, 32);
+ writeValue(loc, r, value);
break;
case ARM64_RELOC_PAGE21:
case ARM64_RELOC_GOT_LOAD_PAGE21:
- case ARM64_RELOC_TLVP_LOAD_PAGE21: {
+ case ARM64_RELOC_TLVP_LOAD_PAGE21:
assert(r.pcrel);
- value = encodePage21(r, base, pageBits(value) - pageBits(pc));
+ encodePage21(loc32, r, base, pageBits(value) - pageBits(pc));
break;
- }
case ARM64_RELOC_PAGEOFF12:
case ARM64_RELOC_GOT_LOAD_PAGEOFF12:
case ARM64_RELOC_TLVP_LOAD_PAGEOFF12:
assert(!r.pcrel);
- value = encodePageOff12(base, value);
+ encodePageOff12(loc32, r, base, value);
break;
default:
llvm_unreachable("unexpected relocation type");
}
-
- switch (r.length) {
- case 2:
- write32le(loc, value);
- break;
- case 3:
- write64le(loc, value);
- break;
- default:
- llvm_unreachable("invalid r_length");
- }
}
void ARM64Common::relaxGotLoad(uint8_t *loc, uint8_t type) const {
instruction = ((instruction & 0x001fffff) | 0x91000000);
write32le(loc, instruction);
}
+
+void ARM64Common::handleDtraceReloc(const Symbol *sym, const Reloc &r,
+ uint8_t *loc) const {
+ assert(r.type == ARM64_RELOC_BRANCH26);
+
+ if (config->outputType == MH_OBJECT)
+ return;
+
+ if (sym->getName().startswith("___dtrace_probe")) {
+ // change call site to a NOP
+ write32le(loc, 0xD503201F);
+ } else if (sym->getName().startswith("___dtrace_isenabled")) {
+ // change call site to 'MOVZ X0,0'
+ write32le(loc, 0xD2800000);
+ } else {
+ error("Unrecognized dtrace symbol prefix: " + toString(*sym));
+ }
+}
+
+static void reportUnalignedLdrStr(Twine loc, uint64_t va, int align,
+ const Symbol *sym) {
+ std::string symbolHint;
+ if (sym)
+ symbolHint = " (" + toString(*sym) + ")";
+ error(loc + ": " + Twine(8 * align) + "-bit LDR/STR to 0x" +
+ llvm::utohexstr(va) + symbolHint + " is not " + Twine(align) +
+ "-byte aligned");
+}
+
+void macho::reportUnalignedLdrStr(void *loc, const lld::macho::Reloc &r,
+ uint64_t va, int align) {
+ uint64_t off = reinterpret_cast<const uint8_t *>(loc) - in.bufferStart;
+ const InputSection *isec = offsetToInputSection(&off);
+ std::string locStr = isec ? isec->getLocation(off) : "(invalid location)";
+ ::reportUnalignedLdrStr(locStr, va, align, r.referent.dyn_cast<Symbol *>());
+}
+
+void macho::reportUnalignedLdrStr(void *loc, lld::macho::SymbolDiagnostic d,
+ uint64_t va, int align) {
+ ::reportUnalignedLdrStr(d.reason, va, align, d.symbol);
+}
#include "llvm/BinaryFormat/MachO.h"
-namespace lld {
-namespace macho {
+namespace lld::macho {
struct ARM64Common : TargetInfo {
template <class LP> ARM64Common(LP lp) : TargetInfo(lp) {}
void relaxGotLoad(uint8_t *loc, uint8_t type) const override;
uint64_t getPageSize() const override { return 16 * 1024; }
+
+ void handleDtraceReloc(const Symbol *sym, const Reloc &r,
+ uint8_t *loc) const override;
};
inline uint64_t bitField(uint64_t value, int right, int width, int left) {
// | | imm26 |
// +-----------+---------------------------------------------------+
-inline uint64_t encodeBranch26(const Reloc &r, uint64_t base, uint64_t va) {
- checkInt(r, va, 28);
+inline void encodeBranch26(uint32_t *loc, const Reloc &r, uint32_t base,
+ uint64_t va) {
+ checkInt(loc, r, va, 28);
// Since branch destinations are 4-byte aligned, the 2 least-
// significant bits are 0. They are right shifted off the end.
- return (base | bitField(va, 2, 26, 0));
+ llvm::support::endian::write32le(loc, base | bitField(va, 2, 26, 0));
}
-inline uint64_t encodeBranch26(SymbolDiagnostic d, uint64_t base, uint64_t va) {
- checkInt(d, va, 28);
- return (base | bitField(va, 2, 26, 0));
+inline void encodeBranch26(uint32_t *loc, SymbolDiagnostic d, uint32_t base,
+ uint64_t va) {
+ checkInt(loc, d, va, 28);
+ llvm::support::endian::write32le(loc, base | bitField(va, 2, 26, 0));
}
// 30 29 23 5
// | |ilo| | immhi | |
// +-+---+---------+-------------------------------------+---------+
-inline uint64_t encodePage21(const Reloc &r, uint64_t base, uint64_t va) {
- checkInt(r, va, 35);
- return (base | bitField(va, 12, 2, 29) | bitField(va, 14, 19, 5));
+inline void encodePage21(uint32_t *loc, const Reloc &r, uint32_t base,
+ uint64_t va) {
+ checkInt(loc, r, va, 35);
+ llvm::support::endian::write32le(loc, base | bitField(va, 12, 2, 29) |
+ bitField(va, 14, 19, 5));
}
-inline uint64_t encodePage21(SymbolDiagnostic d, uint64_t base, uint64_t va) {
- checkInt(d, va, 35);
- return (base | bitField(va, 12, 2, 29) | bitField(va, 14, 19, 5));
+inline void encodePage21(uint32_t *loc, SymbolDiagnostic d, uint32_t base,
+ uint64_t va) {
+ checkInt(loc, d, va, 35);
+ llvm::support::endian::write32le(loc, base | bitField(va, 12, 2, 29) |
+ bitField(va, 14, 19, 5));
}
+void reportUnalignedLdrStr(void *loc, const Reloc &, uint64_t va, int align);
+void reportUnalignedLdrStr(void *loc, SymbolDiagnostic, uint64_t va, int align);
+
// 21 10
// +-------------------+-----------------------+-------------------+
// | | imm12 | |
// +-------------------+-----------------------+-------------------+
-inline uint64_t encodePageOff12(uint32_t base, uint64_t va) {
+template <typename Target>
+inline void encodePageOff12(uint32_t *loc, Target t, uint32_t base,
+ uint64_t va) {
int scale = 0;
if ((base & 0x3b00'0000) == 0x3900'0000) { // load/store
scale = base >> 30;
if (scale == 0 && (base & 0x0480'0000) == 0x0480'0000) // 128-bit variant
scale = 4;
}
+ const int size = 1 << scale;
+ if ((va & (size - 1)) != 0)
+ reportUnalignedLdrStr(loc, t, va, size);
// TODO(gkm): extract embedded addend and warn if != 0
// uint64_t addend = ((base & 0x003FFC00) >> 10);
- return (base | bitField(va, scale, 12 - scale, 10));
+ llvm::support::endian::write32le(loc,
+ base | bitField(va, scale, 12 - scale, 10));
}
inline uint64_t pageBits(uint64_t address) {
return address & pageMask;
}
-template <class LP>
inline void writeStub(uint8_t *buf8, const uint32_t stubCode[3],
- const macho::Symbol &sym) {
+ const macho::Symbol &sym, uint64_t pointerVA) {
auto *buf32 = reinterpret_cast<uint32_t *>(buf8);
constexpr size_t stubCodeSize = 3 * sizeof(uint32_t);
+ SymbolDiagnostic d = {&sym, "stub"};
uint64_t pcPageBits =
pageBits(in.stubs->addr + sym.stubsIndex * stubCodeSize);
- uint64_t lazyPointerVA =
- in.lazyPointers->addr + sym.stubsIndex * LP::wordSize;
- buf32[0] = encodePage21({&sym, "stub"}, stubCode[0],
- pageBits(lazyPointerVA) - pcPageBits);
- buf32[1] = encodePageOff12(stubCode[1], lazyPointerVA);
+ encodePage21(&buf32[0], d, stubCode[0], pageBits(pointerVA) - pcPageBits);
+ encodePageOff12(&buf32[1], d, stubCode[1], pointerVA);
buf32[2] = stubCode[2];
}
};
uint64_t loaderVA = in.imageLoaderCache->getVA();
SymbolDiagnostic d = {nullptr, "stub header helper"};
- buf32[0] = encodePage21(d, stubHelperHeaderCode[0],
- pageBits(loaderVA) - pcPageBits(0));
- buf32[1] = encodePageOff12(stubHelperHeaderCode[1], loaderVA);
+ encodePage21(&buf32[0], d, stubHelperHeaderCode[0],
+ pageBits(loaderVA) - pcPageBits(0));
+ encodePageOff12(&buf32[1], d, stubHelperHeaderCode[1], loaderVA);
buf32[2] = stubHelperHeaderCode[2];
uint64_t binderVA =
in.got->addr + in.stubHelper->stubBinder->gotIndex * LP::wordSize;
- buf32[3] = encodePage21(d, stubHelperHeaderCode[3],
- pageBits(binderVA) - pcPageBits(3));
- buf32[4] = encodePageOff12(stubHelperHeaderCode[4], binderVA);
+ encodePage21(&buf32[3], d, stubHelperHeaderCode[3],
+ pageBits(binderVA) - pcPageBits(3));
+ encodePageOff12(&buf32[4], d, stubHelperHeaderCode[4], binderVA);
buf32[5] = stubHelperHeaderCode[5];
}
inline void writeStubHelperEntry(uint8_t *buf8,
const uint32_t stubHelperEntryCode[3],
- const DylibSymbol &sym, uint64_t entryVA) {
+ const Symbol &sym, uint64_t entryVA) {
auto *buf32 = reinterpret_cast<uint32_t *>(buf8);
auto pcVA = [entryVA](int i) { return entryVA + i * sizeof(uint32_t); };
uint64_t stubHelperHeaderVA = in.stubHelper->addr;
buf32[0] = stubHelperEntryCode[0];
- buf32[1] = encodeBranch26({&sym, "stub helper"}, stubHelperEntryCode[1],
- stubHelperHeaderVA - pcVA(1));
+ encodeBranch26(&buf32[1], {&sym, "stub helper"}, stubHelperEntryCode[1],
+ stubHelperHeaderVA - pcVA(1));
buf32[2] = sym.lazyBindOffset;
}
-} // namespace macho
-} // namespace lld
+template <class LP>
+inline void
+writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
+ Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
+ uint64_t selrefsVA, uint64_t selectorIndex,
+ uint64_t gotAddr, uint64_t msgSendIndex) {
+ SymbolDiagnostic d = {sym, sym->getName()};
+ auto *buf32 = reinterpret_cast<uint32_t *>(buf);
+
+ auto pcPageBits = [stubsAddr, stubOffset](int i) {
+ return pageBits(stubsAddr + stubOffset + i * sizeof(uint32_t));
+ };
+
+ uint64_t selectorOffset = selectorIndex * LP::wordSize;
+ encodePage21(&buf32[0], d, objcStubsFastCode[0],
+ pageBits(selrefsVA + selectorOffset) - pcPageBits(0));
+ encodePageOff12(&buf32[1], d, objcStubsFastCode[1],
+ selrefsVA + selectorOffset);
+ encodePage21(&buf32[2], d, objcStubsFastCode[2],
+ pageBits(gotAddr) - pcPageBits(2));
+ encodePage21(&buf32[3], d, objcStubsFastCode[3], msgSendIndex * LP::wordSize);
+ buf32[4] = objcStubsFastCode[4];
+ buf32[5] = objcStubsFastCode[5];
+ buf32[6] = objcStubsFastCode[6];
+ buf32[7] = objcStubsFastCode[7];
+}
+
+} // namespace lld::macho
#endif
struct ARM64_32 : ARM64Common {
ARM64_32();
- void writeStub(uint8_t *buf, const Symbol &) const override;
+ void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
void writeStubHelperHeader(uint8_t *buf) const override;
- void writeStubHelperEntry(uint8_t *buf, const DylibSymbol &,
+ void writeStubHelperEntry(uint8_t *buf, const Symbol &,
uint64_t entryAddr) const override;
- const RelocAttrs &getRelocAttrs(uint8_t type) const override;
+ void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
+ uint64_t stubOffset, uint64_t selrefsVA,
+ uint64_t selectorIndex, uint64_t gotAddr,
+ uint64_t msgSendIndex) const override;
};
} // namespace
// These are very similar to ARM64's relocation attributes, except that we don't
// have the BYTE8 flag set.
-const RelocAttrs &ARM64_32::getRelocAttrs(uint8_t type) const {
- static const std::array<RelocAttrs, 11> relocAttrsArray{{
+static constexpr std::array<RelocAttrs, 11> relocAttrsArray{{
#define B(x) RelocAttrBits::x
- {"UNSIGNED", B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
- {"SUBTRACTOR", B(SUBTRAHEND) | B(EXTERN) | B(BYTE4)},
- {"BRANCH26", B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
- {"PAGE21", B(PCREL) | B(EXTERN) | B(BYTE4)},
- {"PAGEOFF12", B(ABSOLUTE) | B(EXTERN) | B(BYTE4)},
- {"GOT_LOAD_PAGE21", B(PCREL) | B(EXTERN) | B(GOT) | B(BYTE4)},
- {"GOT_LOAD_PAGEOFF12",
- B(ABSOLUTE) | B(EXTERN) | B(GOT) | B(LOAD) | B(BYTE4)},
- {"POINTER_TO_GOT", B(PCREL) | B(EXTERN) | B(GOT) | B(POINTER) | B(BYTE4)},
- {"TLVP_LOAD_PAGE21", B(PCREL) | B(EXTERN) | B(TLV) | B(BYTE4)},
- {"TLVP_LOAD_PAGEOFF12",
- B(ABSOLUTE) | B(EXTERN) | B(TLV) | B(LOAD) | B(BYTE4)},
- {"ADDEND", B(ADDEND)},
+ {"UNSIGNED", B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
+ {"SUBTRACTOR", B(SUBTRAHEND) | B(EXTERN) | B(BYTE4)},
+ {"BRANCH26", B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
+ {"PAGE21", B(PCREL) | B(EXTERN) | B(BYTE4)},
+ {"PAGEOFF12", B(ABSOLUTE) | B(EXTERN) | B(BYTE4)},
+ {"GOT_LOAD_PAGE21", B(PCREL) | B(EXTERN) | B(GOT) | B(BYTE4)},
+ {"GOT_LOAD_PAGEOFF12",
+ B(ABSOLUTE) | B(EXTERN) | B(GOT) | B(LOAD) | B(BYTE4)},
+ {"POINTER_TO_GOT", B(PCREL) | B(EXTERN) | B(GOT) | B(POINTER) | B(BYTE4)},
+ {"TLVP_LOAD_PAGE21", B(PCREL) | B(EXTERN) | B(TLV) | B(BYTE4)},
+ {"TLVP_LOAD_PAGEOFF12",
+ B(ABSOLUTE) | B(EXTERN) | B(TLV) | B(LOAD) | B(BYTE4)},
+ {"ADDEND", B(ADDEND)},
#undef B
- }};
- assert(type < relocAttrsArray.size() && "invalid relocation type");
- if (type >= relocAttrsArray.size())
- return invalidRelocAttrs;
- return relocAttrsArray[type];
-}
+}};
// The stub code is fairly similar to ARM64's, except that we load pointers into
// 32-bit 'w' registers, instead of the 64-bit 'x' ones.
0xd61f0200, // 08: br x16
};
-void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym) const {
- ::writeStub<ILP32>(buf8, stubCode, sym);
+void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym,
+ uint64_t pointerVA) const {
+ ::writeStub(buf8, stubCode, sym, pointerVA);
}
static constexpr uint32_t stubHelperHeaderCode[] = {
0x00000000, // 08: l0: .long 0
};
-void ARM64_32::writeStubHelperEntry(uint8_t *buf8, const DylibSymbol &sym,
+void ARM64_32::writeStubHelperEntry(uint8_t *buf8, const Symbol &sym,
uint64_t entryVA) const {
::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA);
}
+void ARM64_32::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym,
+ uint64_t stubsAddr, uint64_t stubOffset,
+ uint64_t selrefsVA, uint64_t selectorIndex,
+ uint64_t gotAddr,
+ uint64_t msgSendIndex) const {
+ fatal("TODO: implement this");
+}
+
ARM64_32::ARM64_32() : ARM64Common(ILP32()) {
cpuType = CPU_TYPE_ARM64_32;
cpuSubtype = CPU_SUBTYPE_ARM64_V8;
+ modeDwarfEncoding = 0x04000000; // UNWIND_ARM_MODE_DWARF
+ subtractorRelocType = GENERIC_RELOC_INVALID; // FIXME
+ unsignedRelocType = GENERIC_RELOC_INVALID; // FIXME
+
stubSize = sizeof(stubCode);
stubHelperHeaderSize = sizeof(stubHelperHeaderCode);
stubHelperEntrySize = sizeof(stubHelperEntryCode);
+
+ relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
}
TargetInfo *macho::createARM64_32TargetInfo() {
#include "Target.h"
#include "lld/Common/ErrorHandler.h"
+#include "mach-o/compact_unwind_encoding.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/Endian.h"
void relocateOne(uint8_t *loc, const Reloc &, uint64_t va,
uint64_t relocVA) const override;
- void writeStub(uint8_t *buf, const Symbol &) const override;
+ void writeStub(uint8_t *buf, const Symbol &,
+ uint64_t pointerVA) const override;
void writeStubHelperHeader(uint8_t *buf) const override;
- void writeStubHelperEntry(uint8_t *buf, const DylibSymbol &,
+ void writeStubHelperEntry(uint8_t *buf, const Symbol &,
uint64_t entryAddr) const override;
+ void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
+ uint64_t stubOffset, uint64_t selrefsVA,
+ uint64_t selectorIndex, uint64_t gotAddr,
+ uint64_t msgSendIndex) const override;
+
void relaxGotLoad(uint8_t *loc, uint8_t type) const override;
- const RelocAttrs &getRelocAttrs(uint8_t type) const override;
uint64_t getPageSize() const override { return 4 * 1024; }
-};
+ void handleDtraceReloc(const Symbol *sym, const Reloc &r,
+ uint8_t *loc) const override;
+};
} // namespace
-const RelocAttrs &X86_64::getRelocAttrs(uint8_t type) const {
- static const std::array<RelocAttrs, 10> relocAttrsArray{{
+static constexpr std::array<RelocAttrs, 10> relocAttrsArray{{
#define B(x) RelocAttrBits::x
- {"UNSIGNED",
- B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4) | B(BYTE8)},
- {"SIGNED", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
- {"BRANCH", B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
- {"GOT_LOAD", B(PCREL) | B(EXTERN) | B(GOT) | B(LOAD) | B(BYTE4)},
- {"GOT", B(PCREL) | B(EXTERN) | B(GOT) | B(POINTER) | B(BYTE4)},
- {"SUBTRACTOR", B(SUBTRAHEND) | B(EXTERN) | B(BYTE4) | B(BYTE8)},
- {"SIGNED_1", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
- {"SIGNED_2", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
- {"SIGNED_4", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
- {"TLV", B(PCREL) | B(EXTERN) | B(TLV) | B(LOAD) | B(BYTE4)},
+ {"UNSIGNED",
+ B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4) | B(BYTE8)},
+ {"SIGNED", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
+ {"BRANCH", B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
+ {"GOT_LOAD", B(PCREL) | B(EXTERN) | B(GOT) | B(LOAD) | B(BYTE4)},
+ {"GOT", B(PCREL) | B(EXTERN) | B(GOT) | B(POINTER) | B(BYTE4)},
+ {"SUBTRACTOR", B(SUBTRAHEND) | B(EXTERN) | B(BYTE4) | B(BYTE8)},
+ {"SIGNED_1", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
+ {"SIGNED_2", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
+ {"SIGNED_4", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
+ {"TLV", B(PCREL) | B(EXTERN) | B(TLV) | B(LOAD) | B(BYTE4)},
#undef B
- }};
- assert(type < relocAttrsArray.size() && "invalid relocation type");
- if (type >= relocAttrsArray.size())
- return invalidRelocAttrs;
- return relocAttrsArray[type];
-}
+}};
static int pcrelOffset(uint8_t type) {
switch (type) {
switch (r.length) {
case 2:
if (r.type == X86_64_RELOC_UNSIGNED)
- checkUInt(r, value, 32);
+ checkUInt(loc, r, value, 32);
else
- checkInt(r, value, 32);
+ checkInt(loc, r, value, 32);
write32le(loc, value);
break;
case 3:
static void writeRipRelative(SymbolDiagnostic d, uint8_t *buf, uint64_t bufAddr,
uint64_t bufOff, uint64_t destAddr) {
uint64_t rip = bufAddr + bufOff;
- checkInt(d, destAddr - rip, 32);
+ checkInt(buf, d, destAddr - rip, 32);
// For the instructions we care about, the RIP-relative address is always
// stored in the last 4 bytes of the instruction.
write32le(buf + bufOff - 4, destAddr - rip);
0xff, 0x25, 0, 0, 0, 0, // jmpq *__la_symbol_ptr(%rip)
};
-void X86_64::writeStub(uint8_t *buf, const Symbol &sym) const {
+void X86_64::writeStub(uint8_t *buf, const Symbol &sym,
+ uint64_t pointerVA) const {
memcpy(buf, stub, 2); // just copy the two nonzero bytes
uint64_t stubAddr = in.stubs->addr + sym.stubsIndex * sizeof(stub);
- writeRipRelative({&sym, "stub"}, buf, stubAddr, sizeof(stub),
- in.lazyPointers->addr + sym.stubsIndex * LP64::wordSize);
+ writeRipRelative({&sym, "stub"}, buf, stubAddr, sizeof(stub), pointerVA);
}
static constexpr uint8_t stubHelperHeader[] = {
0xe9, 0, 0, 0, 0, // 0x5: jmp <__stub_helper>
};
-void X86_64::writeStubHelperEntry(uint8_t *buf, const DylibSymbol &sym,
+void X86_64::writeStubHelperEntry(uint8_t *buf, const Symbol &sym,
uint64_t entryAddr) const {
memcpy(buf, stubHelperEntry, sizeof(stubHelperEntry));
write32le(buf + 1, sym.lazyBindOffset);
sizeof(stubHelperEntry), in.stubHelper->addr);
}
+static constexpr uint8_t objcStubsFastCode[] = {
+ 0x48, 0x8b, 0x35, 0, 0, 0, 0, // 0x0: movq selrefs@selector(%rip), %rsi
+ 0xff, 0x25, 0, 0, 0, 0, // 0x7: jmpq *_objc_msgSend@GOT(%rip)
+};
+
+void X86_64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
+ uint64_t stubOffset, uint64_t selrefsVA,
+ uint64_t selectorIndex, uint64_t gotAddr,
+ uint64_t msgSendIndex) const {
+ memcpy(buf, objcStubsFastCode, sizeof(objcStubsFastCode));
+ SymbolDiagnostic d = {sym, sym->getName()};
+ uint64_t stubAddr = stubsAddr + stubOffset;
+ writeRipRelative(d, buf, stubAddr, 7,
+ selrefsVA + selectorIndex * LP64::wordSize);
+ writeRipRelative(d, buf, stubAddr, 0xd,
+ gotAddr + msgSendIndex * LP64::wordSize);
+}
+
void X86_64::relaxGotLoad(uint8_t *loc, uint8_t type) const {
// Convert MOVQ to LEAQ
if (loc[-2] != 0x8b)
cpuType = CPU_TYPE_X86_64;
cpuSubtype = CPU_SUBTYPE_X86_64_ALL;
+ modeDwarfEncoding = UNWIND_X86_MODE_DWARF;
+ subtractorRelocType = X86_64_RELOC_SUBTRACTOR;
+ unsignedRelocType = X86_64_RELOC_UNSIGNED;
+
stubSize = sizeof(stub);
stubHelperHeaderSize = sizeof(stubHelperHeader);
stubHelperEntrySize = sizeof(stubHelperEntry);
+
+ objcStubsFastSize = sizeof(objcStubsFastCode);
+ objcStubsAlignment = 1;
+
+ relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
}
TargetInfo *macho::createX86_64TargetInfo() {
static X86_64 t;
return &t;
}
+
+void X86_64::handleDtraceReloc(const Symbol *sym, const Reloc &r,
+ uint8_t *loc) const {
+ assert(r.type == X86_64_RELOC_BRANCH);
+
+ if (config->outputType == MH_OBJECT)
+ return;
+
+ if (sym->getName().startswith("___dtrace_probe")) {
+ // change call site to a NOP
+ loc[-1] = 0x90;
+ write32le(loc, 0x00401F0F);
+ } else if (sym->getName().startswith("___dtrace_isenabled")) {
+ // change call site to a clear eax
+ loc[-1] = 0x33;
+ write32le(loc, 0x909090C0);
+ } else {
+ error("Unrecognized dtrace symbol prefix: " + toString(*sym));
+ }
+}
include_directories(${LLVM_MAIN_SRC_DIR}/../libunwind/include)
-add_lld_library(lldMachO2
+add_lld_library(lldMachO
Arch/ARM.cpp
Arch/ARM64.cpp
Arch/ARM64Common.cpp
Driver.cpp
DriverUtils.cpp
Dwarf.cpp
+ EhFrame.cpp
ExportTrie.cpp
ICF.cpp
InputFiles.cpp
OutputSection.cpp
OutputSegment.cpp
Relocations.cpp
+ SectionPriorities.cpp
SymbolTable.cpp
Symbols.cpp
SyntheticSections.cpp
${LLVM_TARGETS_TO_BUILD}
BinaryFormat
BitReader
+ BitWriter
Core
DebugInfoDWARF
+ Demangle
LTO
MC
ObjCARCOpts
Option
Passes
Support
+ TargetParser
TextAPI
LINK_LIBS
)
if(LLVM_HAVE_LIBXAR)
- target_link_libraries(lldMachO2 PRIVATE ${XAR_LIB})
+ target_link_libraries(lldMachO PRIVATE ${XAR_LIB})
endif()
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/TimeProfiler.h"
// multiple thunks to the same destination distributed throughout a large
// program so that all call sites can have one within range.
//
-// The optimal approach is to mix islands for distinations within two hops,
+// The optimal approach is to mix islands for destinations within two hops,
// and use thunks for destinations at greater distance. For now, we only
// implement thunks. TODO: Adding support for branch islands!
//
// Internally -- as expressed in LLD's data structures -- a
-// branch-range-extension thunk comprises ...
+// branch-range-extension thunk consists of:
//
-// (1) new Defined privateExtern symbol for the thunk named
+// (1) new Defined symbol for the thunk named
// <FUNCTION>.thunk.<SEQUENCE>, which references ...
// (2) new InputSection, which contains ...
// (3.1) new data for the instructions to load & branch to the far address +
// (3.2) new Relocs on instructions to load the far address, which reference ...
-// (4.1) existing Defined extern symbol for the real function in __text, or
+// (4.1) existing Defined symbol for the real function in __text, or
// (4.2) existing DylibSymbol for the real function in a dylib
//
// Nearly-optimal thunk-placement algorithm features:
// distant call sites might be unable to reach the same thunk, so multiple
// thunks are necessary to serve all call sites in a very large program. A
// thunkInfo stores state for all thunks associated with a particular
-// function: (a) thunk symbol, (b) input section containing stub code, and
-// (c) sequence number for the active thunk incarnation. When an old thunk
-// goes out of range, we increment the sequence number and create a new
-// thunk named <FUNCTION>.thunk.<SEQUENCE>.
+// function:
+// (a) thunk symbol
+// (b) input section containing stub code, and
+// (c) sequence number for the active thunk incarnation.
+// When an old thunk goes out of range, we increment the sequence number and
+// create a new thunk named <FUNCTION>.thunk.<SEQUENCE>.
//
-// * A thunk incarnation comprises (a) private-extern Defined symbol pointing
-// to (b) an InputSection holding machine instructions (similar to a MachO
-// stub), and (c) Reloc(s) that reference the real function for fixing-up
-// the stub code.
+// * A thunk consists of
+// (a) a Defined symbol pointing to
+// (b) an InputSection holding machine code (similar to a MachO stub), and
+// (c) relocs referencing the real function for fixing up the stub code.
//
// * std::vector<InputSection *> MergedInputSection::thunks: A vector parallel
// to the inputs vector. We store new thunks via cheap vector append, rather
// thus, we place thunks at monotonically increasing addresses. Once a thunk
// is placed, it and all previous input-section addresses are final.
//
-// * MergedInputSection::finalize() and MergedInputSection::writeTo() merge
+// * ConcatInputSection::finalize() and ConcatInputSection::writeTo() merge
// the inputs and thunks vectors (both ordered by ascending address), which
// is simple and cheap.
// instructions, whereas CISC (i.e., x86) generally doesn't. RISC only needs
// thunks for programs so large that branch source & destination addresses
// might differ more than the range of branch instruction(s).
-bool ConcatOutputSection::needsThunks() const {
+bool TextOutputSection::needsThunks() const {
if (!target->usesThunks())
return false;
uint64_t isecAddr = addr;
- for (InputSection *isec : inputs)
+ for (ConcatInputSection *isec : inputs)
isecAddr = alignTo(isecAddr, isec->align) + isec->getSize();
- if (isecAddr - addr + in.stubs->getSize() <= target->branchRange)
+ if (isecAddr - addr + in.stubs->getSize() <=
+ std::min(target->backwardBranchRange, target->forwardBranchRange))
return false;
// Yes, this program is large enough to need thunks.
- for (InputSection *isec : inputs) {
+ for (ConcatInputSection *isec : inputs) {
for (Reloc &r : isec->relocs) {
if (!target->hasAttr(r.type, RelocAttrBits::BRANCH))
continue;
auto *sym = r.referent.get<Symbol *>();
// Pre-populate the thunkMap and memoize call site counts for every
// InputSection and ThunkInfo. We do this for the benefit of
- // ConcatOutputSection::estimateStubsInRangeVA()
+ // estimateStubsInRangeVA().
ThunkInfo &thunkInfo = thunkMap[sym];
// Knowing ThunkInfo call site count will help us know whether or not we
// might need to create more for this referent at the time we are
- // estimating distance to __stubs in .
+ // estimating distance to __stubs in estimateStubsInRangeVA().
++thunkInfo.callSiteCount;
- // Knowing InputSection call site count will help us avoid work on those
- // that have no BRANCH relocs.
- ++isec->callSiteCount;
+ // We can avoid work on InputSections that have no BRANCH relocs.
+ isec->hasCallSites = true;
}
}
return true;
// Since __stubs is placed after __text, we must estimate the address
// beyond which stubs are within range of a simple forward branch.
-uint64_t ConcatOutputSection::estimateStubsInRangeVA(size_t callIdx) const {
- uint64_t branchRange = target->branchRange;
- size_t endIdx = inputs.size();
- ConcatInputSection *isec = inputs[callIdx];
- uint64_t isecVA = isec->getVA();
- // Tally the non-stub functions which still have call sites
- // remaining to process, which yields the maximum number
- // of thunks we might yet place.
+// This is called exactly once, when the last input section has been finalized.
+uint64_t TextOutputSection::estimateStubsInRangeVA(size_t callIdx) const {
+ // Tally the functions which still have call sites remaining to process,
+ // which yields the maximum number of thunks we might yet place.
size_t maxPotentialThunks = 0;
for (auto &tp : thunkMap) {
ThunkInfo &ti = tp.second;
- maxPotentialThunks +=
- !tp.first->isInStubs() && ti.callSitesUsed < ti.callSiteCount;
+ // This overcounts: Only sections that are in forward jump range from the
+ // currently-active section get finalized, and all input sections are
+ // finalized when estimateStubsInRangeVA() is called. So only backward
+ // jumps will need thunks, but we count all jumps.
+ if (ti.callSitesUsed < ti.callSiteCount)
+ maxPotentialThunks += 1;
}
// Tally the total size of input sections remaining to process.
- uint64_t isecEnd = isec->getVA();
- for (size_t i = callIdx; i < endIdx; i++) {
+ uint64_t isecVA = inputs[callIdx]->getVA();
+ uint64_t isecEnd = isecVA;
+ for (size_t i = callIdx; i < inputs.size(); i++) {
InputSection *isec = inputs[i];
isecEnd = alignTo(isecEnd, isec->align) + isec->getSize();
}
// Estimate the address after which call sites can safely call stubs
// directly rather than through intermediary thunks.
+ uint64_t forwardBranchRange = target->forwardBranchRange;
+ assert(isecEnd > forwardBranchRange &&
+ "should not run thunk insertion if all code fits in jump range");
+ assert(isecEnd - isecVA <= forwardBranchRange &&
+ "should only finalize sections in jump range");
uint64_t stubsInRangeVA = isecEnd + maxPotentialThunks * target->thunkSize +
- in.stubs->getSize() - branchRange;
+ in.stubs->getSize() - forwardBranchRange;
log("thunks = " + std::to_string(thunkMap.size()) +
", potential = " + std::to_string(maxPotentialThunks) +
", stubs = " + std::to_string(in.stubs->getSize()) + ", isecVA = " +
- to_hexString(isecVA) + ", threshold = " + to_hexString(stubsInRangeVA) +
- ", isecEnd = " + to_hexString(isecEnd) +
- ", tail = " + to_hexString(isecEnd - isecVA) +
- ", slop = " + to_hexString(branchRange - (isecEnd - isecVA)));
+ utohexstr(isecVA) + ", threshold = " + utohexstr(stubsInRangeVA) +
+ ", isecEnd = " + utohexstr(isecEnd) +
+ ", tail = " + utohexstr(isecEnd - isecVA) +
+ ", slop = " + utohexstr(forwardBranchRange - (isecEnd - isecVA)));
return stubsInRangeVA;
}
-void ConcatOutputSection::finalize() {
- uint64_t isecAddr = addr;
- uint64_t isecFileOff = fileOff;
- auto finalizeOne = [&](ConcatInputSection *isec) {
- isecAddr = alignTo(isecAddr, isec->align);
- isecFileOff = alignTo(isecFileOff, isec->align);
- isec->outSecOff = isecAddr - addr;
- isec->isFinal = true;
- isecAddr += isec->getSize();
- isecFileOff += isec->getFileSize();
- };
+void ConcatOutputSection::finalizeOne(ConcatInputSection *isec) {
+ size = alignTo(size, isec->align);
+ fileSize = alignTo(fileSize, isec->align);
+ isec->outSecOff = size;
+ isec->isFinal = true;
+ size += isec->getSize();
+ fileSize += isec->getFileSize();
+}
+
+void ConcatOutputSection::finalizeContents() {
+ for (ConcatInputSection *isec : inputs)
+ finalizeOne(isec);
+}
+void TextOutputSection::finalize() {
if (!needsThunks()) {
for (ConcatInputSection *isec : inputs)
finalizeOne(isec);
- size = isecAddr - addr;
- fileSize = isecFileOff - fileOff;
return;
}
- uint64_t branchRange = target->branchRange;
+ uint64_t forwardBranchRange = target->forwardBranchRange;
+ uint64_t backwardBranchRange = target->backwardBranchRange;
uint64_t stubsInRangeVA = TargetInfo::outOfRangeVA;
size_t thunkSize = target->thunkSize;
size_t relocCount = 0;
size_t thunkCallCount = 0;
size_t thunkCount = 0;
+ // Walk all sections in order. Finalize all sections that are less than
+ // forwardBranchRange in front of it.
+ // isecVA is the address of the current section.
+ // addr + size is the start address of the first non-finalized section.
+
// inputs[finalIdx] is for finalization (address-assignment)
size_t finalIdx = 0;
// Kick-off by ensuring that the first input section has an address
ConcatInputSection *isec = inputs[callIdx];
assert(isec->isFinal);
uint64_t isecVA = isec->getVA();
- // Assign addresses up-to the forward branch-range limit
- while (finalIdx < endIdx &&
- isecAddr + inputs[finalIdx]->getSize() < isecVA + branchRange)
+
+ // Assign addresses up-to the forward branch-range limit.
+ // Every call instruction needs a small number of bytes (on Arm64: 4),
+ // and each inserted thunk needs a slightly larger number of bytes
+ // (on Arm64: 12). If a section starts with a branch instruction and
+ // contains several branch instructions in succession, then the distance
+ // from the current position to the position where the thunks are inserted
+ // grows. So leave room for a bunch of thunks.
+ unsigned slop = 1024 * thunkSize;
+ while (finalIdx < endIdx && addr + size + inputs[finalIdx]->getSize() <
+ isecVA + forwardBranchRange - slop)
finalizeOne(inputs[finalIdx++]);
- if (isec->callSiteCount == 0)
+
+ if (!isec->hasCallSites)
continue;
+
if (finalIdx == endIdx && stubsInRangeVA == TargetInfo::outOfRangeVA) {
// When we have finalized all input sections, __stubs (destined
// to follow __text) comes within range of forward branches and
++callSiteCount;
// Calculate branch reachability boundaries
uint64_t callVA = isecVA + r.offset;
- uint64_t lowVA = branchRange < callVA ? callVA - branchRange : 0;
- uint64_t highVA = callVA + branchRange;
+ uint64_t lowVA =
+ backwardBranchRange < callVA ? callVA - backwardBranchRange : 0;
+ uint64_t highVA = callVA + forwardBranchRange;
// Calculate our call referent address
auto *funcSym = r.referent.get<Symbol *>();
ThunkInfo &thunkInfo = thunkMap[funcSym];
// The referent is not reachable, so we need to use a thunk ...
if (funcSym->isInStubs() && callVA >= stubsInRangeVA) {
+ assert(callVA != TargetInfo::outOfRangeVA);
// ... Oh, wait! We are close enough to the end that __stubs
// are now within range of a simple forward branch.
continue;
}
uint64_t funcVA = funcSym->resolveBranchVA();
++thunkInfo.callSitesUsed;
- if (lowVA < funcVA && funcVA < highVA) {
+ if (lowVA <= funcVA && funcVA <= highVA) {
// The referent is reachable with a simple call instruction.
continue;
}
// If an existing thunk is reachable, use it ...
if (thunkInfo.sym) {
uint64_t thunkVA = thunkInfo.isec->getVA();
- if (lowVA < thunkVA && thunkVA < highVA) {
+ if (lowVA <= thunkVA && thunkVA <= highVA) {
r.referent = thunkInfo.sym;
continue;
}
}
- // ... otherwise, create a new thunk
- if (isecAddr > highVA) {
- // When there is small-to-no margin between highVA and
- // isecAddr and the distance between subsequent call sites is
- // smaller than thunkSize, then a new thunk can go out of
- // range. Fix by unfinalizing inputs[finalIdx] to reduce the
- // distance between callVA and highVA, then shift some thunks
- // to occupy address-space formerly occupied by the
- // unfinalized inputs[finalIdx].
+ // ... otherwise, create a new thunk.
+ if (addr + size > highVA) {
+ // There were too many consecutive branch instructions for `slop`
+ // above. If you hit this: For the current algorithm, just bumping up
+ // slop above and trying again is probably simplest. (See also PR51578
+ // comment 5).
fatal(Twine(__FUNCTION__) + ": FIXME: thunk range overrun");
}
thunkInfo.isec =
- make<ConcatInputSection>(isec->getSegName(), isec->getName());
+ makeSyntheticInputSection(isec->getSegName(), isec->getName());
thunkInfo.isec->parent = this;
- StringRef thunkName = saver.save(funcSym->getName() + ".thunk." +
- std::to_string(thunkInfo.sequence++));
- r.referent = thunkInfo.sym = symtab->addDefined(
- thunkName, /*file=*/nullptr, thunkInfo.isec, /*value=*/0,
- /*size=*/thunkSize, /*isWeakDef=*/false, /*isPrivateExtern=*/true,
- /*isThumb=*/false, /*isReferencedDynamically=*/false,
- /*noDeadStrip=*/false);
+
+ // This code runs after dead code removal. Need to set the `live` bit
+ // on the thunk isec so that asserts that check that only live sections
+ // get written are happy.
+ thunkInfo.isec->live = true;
+
+ StringRef thunkName = saver().save(funcSym->getName() + ".thunk." +
+ std::to_string(thunkInfo.sequence++));
+ if (!isa<Defined>(funcSym) || cast<Defined>(funcSym)->isExternal()) {
+ r.referent = thunkInfo.sym = symtab->addDefined(
+ thunkName, /*file=*/nullptr, thunkInfo.isec, /*value=*/0, thunkSize,
+ /*isWeakDef=*/false, /*isPrivateExtern=*/true,
+ /*isThumb=*/false, /*isReferencedDynamically=*/false,
+ /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+ } else {
+ r.referent = thunkInfo.sym = make<Defined>(
+ thunkName, /*file=*/nullptr, thunkInfo.isec, /*value=*/0, thunkSize,
+ /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/true,
+ /*includeInSymtab=*/true, /*isThumb=*/false,
+ /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
+ /*isWeakDefCanBeHidden=*/false);
+ }
+ thunkInfo.sym->used = true;
target->populateThunk(thunkInfo.isec, funcSym);
finalizeOne(thunkInfo.isec);
thunks.push_back(thunkInfo.isec);
++thunkCount;
}
}
- size = isecAddr - addr;
- fileSize = isecFileOff - fileOff;
log("thunks for " + parent->name + "," + name +
": funcs = " + std::to_string(thunkMap.size()) +
}
void ConcatOutputSection::writeTo(uint8_t *buf) const {
+ for (ConcatInputSection *isec : inputs)
+ isec->writeTo(buf + isec->outSecOff);
+}
+
+void TextOutputSection::writeTo(uint8_t *buf) const {
// Merge input sections from thunk & ordinary vectors
size_t i = 0, ie = inputs.size();
size_t t = 0, te = thunks.size();
while (i < ie || t < te) {
- while (i < ie && (t == te || inputs[i]->getSize() == 0 ||
+ while (i < ie && (t == te || inputs[i]->empty() ||
inputs[i]->outSecOff < thunks[t]->outSecOff)) {
inputs[i]->writeTo(buf + inputs[i]->outSecOff);
++i;
ConcatOutputSection::getOrCreateForInput(const InputSection *isec) {
NamePair names = maybeRenameSection({isec->getSegName(), isec->getName()});
ConcatOutputSection *&osec = concatOutputSections[names];
- if (!osec)
- osec = make<ConcatOutputSection>(names.second);
+ if (!osec) {
+ if (isec->getSegName() == segment_names::text &&
+ isec->getName() != section_names::gccExceptTab &&
+ isec->getName() != section_names::ehFrame)
+ osec = make<TextOutputSection>(names.second);
+ else
+ osec = make<ConcatOutputSection>(names.second);
+ }
return osec;
}
//
//===----------------------------------------------------------------------===//
-#ifndef LLD_MACHO_MERGED_OUTPUT_SECTION_H
-#define LLD_MACHO_MERGED_OUTPUT_SECTION_H
+#ifndef LLD_MACHO_CONCAT_OUTPUT_SECTION_H
+#define LLD_MACHO_CONCAT_OUTPUT_SECTION_H
#include "InputSection.h"
#include "OutputSection.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/MapVector.h"
-namespace lld {
-namespace macho {
+namespace lld::macho {
class Defined;
// files that are labeled with the same segment and section name. This class
// contains all such sections and writes the data from each section sequentially
// in the final binary.
-class ConcatOutputSection final : public OutputSection {
+class ConcatOutputSection : public OutputSection {
public:
explicit ConcatOutputSection(StringRef name)
: OutputSection(ConcatKind, name) {}
uint64_t getSize() const override { return size; }
uint64_t getFileSize() const override { return fileSize; }
- void addInput(ConcatInputSection *input);
- void finalize() override;
- bool needsThunks() const;
- uint64_t estimateStubsInRangeVA(size_t callIdx) const;
+ // Assign values to InputSection::outSecOff. In contrast to TextOutputSection,
+ // which does this in its implementation of `finalize()`, we can do this
+ // without `finalize()`'s sequential guarantees detailed in the block comment
+ // of `OutputSection::finalize()`.
+ virtual void finalizeContents();
+ void addInput(ConcatInputSection *input);
void writeTo(uint8_t *buf) const override;
- std::vector<ConcatInputSection *> inputs;
- std::vector<ConcatInputSection *> thunks;
-
static bool classof(const OutputSection *sec) {
return sec->kind() == ConcatKind;
}
static ConcatOutputSection *getOrCreateForInput(const InputSection *);
-private:
- void finalizeFlags(InputSection *input);
+ std::vector<ConcatInputSection *> inputs;
+protected:
size_t size = 0;
uint64_t fileSize = 0;
+ void finalizeOne(ConcatInputSection *);
+
+private:
+ void finalizeFlags(InputSection *input);
+};
+
+// ConcatOutputSections that contain code (text) require special handling to
+// support thunk insertion.
+class TextOutputSection : public ConcatOutputSection {
+public:
+ explicit TextOutputSection(StringRef name) : ConcatOutputSection(name) {}
+ void finalizeContents() override {}
+ void finalize() override;
+ bool needsThunks() const;
+ void writeTo(uint8_t *buf) const override;
+
+private:
+ uint64_t estimateStubsInRangeVA(size_t callIdx) const;
+
+ std::vector<ConcatInputSection *> thunks;
};
// We maintain one ThunkInfo per real function.
extern llvm::DenseMap<Symbol *, ThunkInfo> thunkMap;
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/BinaryFormat/MachO.h"
namespace lld {
namespace macho {
+class InputSection;
class Symbol;
-struct SymbolPriorityEntry;
using NamePair = std::pair<llvm::StringRef, llvm::StringRef>;
using SectionRenameMap = llvm::DenseMap<NamePair, NamePair>;
inline uint32_t encodeVersion(const llvm::VersionTuple &version) {
return ((version.getMajor() << 020) |
- (version.getMinor().getValueOr(0) << 010) |
- version.getSubminor().getValueOr(0));
+ (version.getMinor().value_or(0) << 010) |
+ version.getSubminor().value_or(0));
}
enum class NamespaceKind {
all,
};
+enum class ObjCStubsMode {
+ fast,
+ small,
+};
+
struct SectionAlign {
llvm::StringRef segName;
llvm::StringRef sectName;
bool match(llvm::StringRef symbolName) const;
};
+enum class SymtabPresence {
+ All,
+ None,
+ SelectivelyIncluded,
+ SelectivelyExcluded,
+};
+
struct Configuration {
Symbol *entry = nullptr;
bool hasReexports = false;
bool archMultiple = false;
bool exportDynamic = false;
bool forceLoadObjC = false;
- bool forceLoadSwift = false;
+ bool forceLoadSwift = false; // Only applies to LC_LINKER_OPTIONs.
bool staticLink = false;
bool implicitDylibs = false;
bool isPic = false;
bool headerPadMaxInstallNames = false;
- bool ltoNewPassManager = LLVM_ENABLE_NEW_PASS_MANAGER;
bool markDeadStrippableDylib = false;
bool printDylibSearch = false;
bool printEachFile = false;
bool emitBitcodeBundle = false;
bool emitDataInCodeInfo = false;
bool emitEncryptionInfo = false;
+ bool emitInitOffsets = false;
+ bool emitChainedFixups = false;
+ bool thinLTOEmitImportsFiles;
+ bool thinLTOEmitIndexFiles;
+ bool thinLTOIndexOnly;
bool timeTraceEnabled = false;
bool dataConst = false;
- bool dedupLiterals = true;
+ bool dedupStrings = true;
+ bool deadStripDuplicates = false;
+ bool omitDebugInfo = false;
+ bool warnDylibInstallName = false;
+ bool ignoreOptimizationHints = false;
+ bool forceExactCpuSubtypeMatch = false;
uint32_t headerPad;
uint32_t dylibCompatibilityVersion = 0;
uint32_t dylibCurrentVersion = 0;
uint32_t ltoo = 2;
llvm::CachePruningPolicy thinLTOCachePolicy;
llvm::StringRef thinLTOCacheDir;
+ llvm::StringRef thinLTOIndexOnlyArg;
+ std::pair<llvm::StringRef, llvm::StringRef> thinLTOObjectSuffixReplace;
+ std::pair<llvm::StringRef, llvm::StringRef> thinLTOPrefixReplace;
bool deadStripDylibs = false;
bool demangle = false;
bool deadStrip = false;
+ bool errorForArchMismatch = false;
+ bool ignoreAutoLink = false;
+ // ld64 allows invalid auto link options as long as the link succeeds. LLD
+ // does not, but there are cases in the wild where the invalid linker options
+ // exist. This allows users to ignore the specific invalid options in the case
+ // they can't easily fix them.
+ llvm::StringSet<> ignoreAutoLinkOptions;
+ bool strictAutoLink = false;
PlatformInfo platformInfo;
+ std::optional<PlatformInfo> secondaryPlatformInfo;
NamespaceKind namespaceKind = NamespaceKind::twolevel;
UndefinedSymbolTreatment undefinedSymbolTreatment =
UndefinedSymbolTreatment::error;
ICFLevel icfLevel = ICFLevel::none;
+ ObjCStubsMode objcStubsMode = ObjCStubsMode::fast;
llvm::MachO::HeaderFileType outputType;
std::vector<llvm::StringRef> systemLibraryRoots;
std::vector<llvm::StringRef> librarySearchPaths;
std::vector<llvm::StringRef> frameworkSearchPaths;
- std::vector<llvm::StringRef> runtimePaths;
+ llvm::SmallVector<llvm::StringRef, 0> runtimePaths;
std::vector<std::string> astPaths;
std::vector<Symbol *> explicitUndefineds;
llvm::StringSet<> explicitDynamicLookups;
std::vector<SectionAlign> sectionAlignments;
std::vector<SegmentProtection> segmentProtections;
- llvm::DenseMap<llvm::StringRef, SymbolPriorityEntry> priorities;
+ bool callGraphProfileSort = false;
+ llvm::StringRef printSymbolOrder;
+
SectionRenameMap sectionRenameMap;
SegmentRenameMap segmentRenameMap;
+ bool hasExplicitExports = false;
SymbolPatterns exportedSymbols;
SymbolPatterns unexportedSymbols;
+ SymbolPatterns whyLive;
+
+ std::vector<std::pair<llvm::StringRef, llvm::StringRef>> aliasedSymbols;
- bool zeroModTime = false;
+ SymtabPresence localSymbolsPresence = SymtabPresence::All;
+ SymbolPatterns localSymbolPatterns;
+ llvm::SmallVector<llvm::StringRef, 0> mllvmOpts;
+
+ bool zeroModTime = true;
+
+ llvm::StringRef osoPrefix;
+
+ std::vector<llvm::StringRef> dyldEnvs;
llvm::MachO::Architecture arch() const { return platformInfo.target.Arch; }
- llvm::MachO::PlatformKind platform() const {
+ llvm::MachO::PlatformType platform() const {
return platformInfo.target.Platform;
}
};
-// The symbol with the highest priority should be ordered first in the output
-// section (modulo input section contiguity constraints). Using priority
-// (highest first) instead of order (lowest first) has the convenient property
-// that the default-constructed zero priority -- for symbols/sections without a
-// user-defined order -- naturally ends up putting them at the end of the
-// output.
-struct SymbolPriorityEntry {
- // The priority given to a matching symbol, regardless of which object file
- // it originated from.
- size_t anyObjectFile = 0;
- // The priority given to a matching symbol from a particular object file.
- llvm::DenseMap<llvm::StringRef, size_t> objectFiles;
-};
-
-extern Configuration *config;
+extern std::unique_ptr<Configuration> config;
} // namespace macho
} // namespace lld
#include "ObjC.h"
#include "OutputSection.h"
#include "OutputSegment.h"
+#include "SectionPriorities.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Writer.h"
#include "lld/Common/Args.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Driver.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/LLVM.h"
using namespace lld;
using namespace lld::macho;
-Configuration *macho::config;
-DependencyTracker *macho::depTracker;
+std::unique_ptr<Configuration> macho::config;
+std::unique_ptr<DependencyTracker> macho::depTracker;
static HeaderFileType getOutputType(const InputArgList &args) {
// TODO: -r, -dylinker, -preload...
}
}
-static Optional<StringRef> findLibrary(StringRef name) {
- if (config->searchDylibsFirst) {
- if (Optional<StringRef> path = findPathCombination(
- "lib" + name, config->librarySearchPaths, {".tbd", ".dylib"}))
- return path;
+static DenseMap<CachedHashStringRef, StringRef> resolvedLibraries;
+static std::optional<StringRef> findLibrary(StringRef name) {
+ CachedHashStringRef key(name);
+ auto entry = resolvedLibraries.find(key);
+ if (entry != resolvedLibraries.end())
+ return entry->second;
+
+ auto doFind = [&] {
+ if (config->searchDylibsFirst) {
+ if (std::optional<StringRef> path = findPathCombination(
+ "lib" + name, config->librarySearchPaths, {".tbd", ".dylib"}))
+ return path;
+ return findPathCombination("lib" + name, config->librarySearchPaths,
+ {".a"});
+ }
return findPathCombination("lib" + name, config->librarySearchPaths,
- {".a"});
- }
- return findPathCombination("lib" + name, config->librarySearchPaths,
- {".tbd", ".dylib", ".a"});
+ {".tbd", ".dylib", ".a"});
+ };
+
+ std::optional<StringRef> path = doFind();
+ if (path)
+ resolvedLibraries[key] = *path;
+
+ return path;
}
-static Optional<std::string> findFramework(StringRef name) {
+static DenseMap<CachedHashStringRef, StringRef> resolvedFrameworks;
+static std::optional<StringRef> findFramework(StringRef name) {
+ CachedHashStringRef key(name);
+ auto entry = resolvedFrameworks.find(key);
+ if (entry != resolvedFrameworks.end())
+ return entry->second;
+
SmallString<260> symlink;
StringRef suffix;
std::tie(name, suffix) = name.split(",");
// only append suffix if realpath() succeeds
Twine suffixed = location + suffix;
if (fs::exists(suffixed))
- return suffixed.str();
+ return resolvedFrameworks[key] = saver().save(suffixed.str());
}
// Suffix lookup failed, fall through to the no-suffix case.
}
- if (Optional<std::string> path = resolveDylibPath(symlink))
- return path;
+ if (std::optional<StringRef> path = resolveDylibPath(symlink.str()))
+ return resolvedFrameworks[key] = *path;
}
return {};
}
path::append(buffer, path);
// Do not warn about paths that are computed via the syslib roots
if (fs::is_directory(buffer)) {
- paths.push_back(saver.save(buffer.str()));
+ paths.push_back(saver().save(buffer.str()));
found = true;
}
}
SmallString<261> buffer(root);
path::append(buffer, path);
if (fs::is_directory(buffer))
- paths.push_back(saver.save(buffer.str()));
+ paths.push_back(saver().save(buffer.str()));
}
}
return paths;
for (const Arg *arg : args.filtered(OPT_syslibroot))
roots.push_back(arg->getValue());
// NOTE: the final `-syslibroot` being `/` will ignore all roots
- if (roots.size() && roots.back() == "/")
+ if (!roots.empty() && roots.back() == "/")
roots.clear();
// NOTE: roots can never be empty - add an empty root to simplify the library
// and framework search path computation.
val.toVector(ltoPolicy);
};
for (const Arg *arg :
- args.filtered(OPT_thinlto_cache_policy, OPT_prune_interval_lto,
+ args.filtered(OPT_thinlto_cache_policy_eq, OPT_prune_interval_lto,
OPT_prune_after_lto, OPT_max_relative_cache_size_lto)) {
switch (arg->getOption().getID()) {
- case OPT_thinlto_cache_policy: add(arg->getValue()); break;
+ case OPT_thinlto_cache_policy_eq:
+ add(arg->getValue());
+ break;
case OPT_prune_interval_lto:
if (!strcmp("-1", arg->getValue()))
add("prune_interval=87600h"); // 10 years
return CHECK(parseCachePruningPolicy(ltoPolicy), "invalid LTO cache policy");
}
-namespace {
-struct ArchiveMember {
- MemoryBufferRef mbref;
- uint32_t modTime;
- uint64_t offsetInArchive;
+// What caused a given library to be loaded. Only relevant for archives.
+// Note that this does not tell us *how* we should load the library, i.e.
+// whether we should do it lazily or eagerly (AKA force loading). The "how" is
+// decided within addFile().
+enum class LoadType {
+ CommandLine, // Library was passed as a regular CLI argument
+ CommandLineForce, // Library was passed via `-force_load`
+ LCLinkerOption, // Library was passed via LC_LINKER_OPTIONS
+};
+
+struct ArchiveFileInfo {
+ ArchiveFile *file;
+ bool isCommandLineLoad;
};
-} // namespace
-
-// Returns slices of MB by parsing MB as an archive file.
-// Each slice consists of a member file in the archive.
-static std::vector<ArchiveMember> getArchiveMembers(MemoryBufferRef mb) {
- std::unique_ptr<Archive> file =
- CHECK(Archive::create(mb),
- mb.getBufferIdentifier() + ": failed to parse archive");
- Archive *archive = file.get();
- make<std::unique_ptr<Archive>>(std::move(file)); // take ownership
-
- std::vector<ArchiveMember> v;
- Error err = Error::success();
-
- // Thin archives refer to .o files, so --reproduce needs the .o files too.
- bool addToTar = archive->isThin() && tar;
-
- for (const Archive::Child &c : archive->children(err)) {
- MemoryBufferRef mbref =
- CHECK(c.getMemoryBufferRef(),
- mb.getBufferIdentifier() +
- ": could not get the buffer for a child of the archive");
- if (addToTar)
- tar->append(relativeToRoot(check(c.getFullName())), mbref.getBuffer());
- uint32_t modTime = toTimeT(
- CHECK(c.getLastModified(), mb.getBufferIdentifier() +
- ": could not get the modification "
- "time for a child of the archive"));
- v.push_back({mbref, modTime, c.getChildOffset()});
- }
- if (err)
- fatal(mb.getBufferIdentifier() +
- ": Archive::children failed: " + toString(std::move(err)));
-
- return v;
-}
-static DenseMap<StringRef, ArchiveFile *> loadedArchives;
+static DenseMap<StringRef, ArchiveFileInfo> loadedArchives;
-static InputFile *addFile(StringRef path, bool forceLoadArchive,
- bool isExplicit = true, bool isBundleLoader = false) {
- Optional<MemoryBufferRef> buffer = readFile(path);
+static InputFile *addFile(StringRef path, LoadType loadType,
+ bool isLazy = false, bool isExplicit = true,
+ bool isBundleLoader = false,
+ bool isForceHidden = false) {
+ std::optional<MemoryBufferRef> buffer = readFile(path);
if (!buffer)
return nullptr;
MemoryBufferRef mbref = *buffer;
file_magic magic = identify_magic(mbref.getBuffer());
switch (magic) {
case file_magic::archive: {
+ bool isCommandLineLoad = loadType != LoadType::LCLinkerOption;
// Avoid loading archives twice. If the archives are being force-loaded,
// loading them twice would create duplicate symbol errors. In the
// non-force-loading case, this is just a minor performance optimization.
// We don't take a reference to cachedFile here because the
// loadArchiveMember() call below may recursively call addFile() and
// invalidate this reference.
- if (ArchiveFile *cachedFile = loadedArchives[path])
- return cachedFile;
-
- std::unique_ptr<object::Archive> file = CHECK(
- object::Archive::create(mbref), path + ": failed to parse archive");
-
- if (!file->isEmpty() && !file->hasSymbolTable())
- error(path + ": archive has no index; run ranlib to add one");
-
- if (config->allLoad || forceLoadArchive) {
- if (Optional<MemoryBufferRef> buffer = readFile(path)) {
- for (const ArchiveMember &member : getArchiveMembers(*buffer)) {
- if (Optional<InputFile *> file = loadArchiveMember(
- member.mbref, member.modTime, path, /*objCOnly=*/false,
- member.offsetInArchive)) {
- inputFiles.insert(*file);
- printArchiveMemberLoad(
- (forceLoadArchive ? "-force_load" : "-all_load"),
- inputFiles.back());
+ auto entry = loadedArchives.find(path);
+
+ ArchiveFile *file;
+ if (entry == loadedArchives.end()) {
+ // No cached archive, we need to create a new one
+ std::unique_ptr<object::Archive> archive = CHECK(
+ object::Archive::create(mbref), path + ": failed to parse archive");
+
+ if (!archive->isEmpty() && !archive->hasSymbolTable())
+ error(path + ": archive has no index; run ranlib to add one");
+ file = make<ArchiveFile>(std::move(archive), isForceHidden);
+ } else {
+ file = entry->second.file;
+ // Command-line loads take precedence. If file is previously loaded via
+ // command line, or is loaded via LC_LINKER_OPTION and being loaded via
+ // LC_LINKER_OPTION again, using the cached archive is enough.
+ if (entry->second.isCommandLineLoad || !isCommandLineLoad)
+ return file;
+ }
+
+ bool isLCLinkerForceLoad = loadType == LoadType::LCLinkerOption &&
+ config->forceLoadSwift &&
+ path::filename(path).startswith("libswift");
+ if ((isCommandLineLoad && config->allLoad) ||
+ loadType == LoadType::CommandLineForce || isLCLinkerForceLoad) {
+ if (std::optional<MemoryBufferRef> buffer = readFile(path)) {
+ Error e = Error::success();
+ for (const object::Archive::Child &c : file->getArchive().children(e)) {
+ StringRef reason;
+ switch (loadType) {
+ case LoadType::LCLinkerOption:
+ reason = "LC_LINKER_OPTION";
+ break;
+ case LoadType::CommandLineForce:
+ reason = "-force_load";
+ break;
+ case LoadType::CommandLine:
+ reason = "-all_load";
+ break;
}
+ if (Error e = file->fetch(c, reason))
+ error(toString(file) + ": " + reason +
+ " failed to load archive member: " + toString(std::move(e)));
}
+ if (e)
+ error(toString(file) +
+ ": Archive::children failed: " + toString(std::move(e)));
}
- } else if (config->forceLoadObjC) {
- for (const object::Archive::Symbol &sym : file->symbols())
+ } else if (isCommandLineLoad && config->forceLoadObjC) {
+ for (const object::Archive::Symbol &sym : file->getArchive().symbols())
if (sym.getName().startswith(objc::klass))
- symtab->addUndefined(sym.getName(), /*file=*/nullptr,
- /*isWeakRef=*/false);
+ file->fetch(sym);
// TODO: no need to look for ObjC sections for a given archive member if
- // we already found that it contains an ObjC symbol. We should also
- // consider creating a LazyObjFile class in order to avoid double-loading
- // these files here and below (as part of the ArchiveFile).
- if (Optional<MemoryBufferRef> buffer = readFile(path)) {
- for (const ArchiveMember &member : getArchiveMembers(*buffer)) {
- if (Optional<InputFile *> file = loadArchiveMember(
- member.mbref, member.modTime, path, /*objCOnly=*/true,
- member.offsetInArchive)) {
- inputFiles.insert(*file);
- printArchiveMemberLoad("-ObjC", inputFiles.back());
- }
+ // we already found that it contains an ObjC symbol.
+ if (std::optional<MemoryBufferRef> buffer = readFile(path)) {
+ Error e = Error::success();
+ for (const object::Archive::Child &c : file->getArchive().children(e)) {
+ Expected<MemoryBufferRef> mb = c.getMemoryBufferRef();
+ if (!mb || !hasObjCSection(*mb))
+ continue;
+ if (Error e = file->fetch(c, "-ObjC"))
+ error(toString(file) + ": -ObjC failed to load archive member: " +
+ toString(std::move(e)));
}
+ if (e)
+ error(toString(file) +
+ ": Archive::children failed: " + toString(std::move(e)));
}
}
- newFile = loadedArchives[path] = make<ArchiveFile>(std::move(file));
+ file->addLazySymbols();
+ loadedArchives[path] = ArchiveFileInfo{file, isCommandLineLoad};
+ newFile = file;
break;
}
case file_magic::macho_object:
- newFile = make<ObjFile>(mbref, getModTime(path), "");
+ newFile = make<ObjFile>(mbref, getModTime(path), "", isLazy);
break;
case file_magic::macho_dynamically_linked_shared_lib:
case file_magic::macho_dynamically_linked_shared_lib_stub:
case file_magic::tapi_file:
- if (DylibFile *dylibFile = loadDylib(mbref)) {
- if (isExplicit)
- dylibFile->explicitlyLinked = true;
+ if (DylibFile *dylibFile =
+ loadDylib(mbref, nullptr, /*isBundleLoader=*/false, isExplicit))
newFile = dylibFile;
- }
break;
case file_magic::bitcode:
- newFile = make<BitcodeFile>(mbref, "", 0);
+ newFile = make<BitcodeFile>(mbref, "", 0, isLazy);
break;
case file_magic::macho_executable:
case file_magic::macho_bundle:
error(path + ": unhandled file type");
}
if (newFile && !isa<DylibFile>(newFile)) {
+ if ((isa<ObjFile>(newFile) || isa<BitcodeFile>(newFile)) && newFile->lazy &&
+ config->forceLoadObjC) {
+ for (Symbol *sym : newFile->symbols)
+ if (sym && sym->getName().startswith(objc::klass)) {
+ extract(*newFile, "-ObjC");
+ break;
+ }
+ if (newFile->lazy && hasObjCSection(mbref))
+ extract(*newFile, "-ObjC");
+ }
+
// printArchiveMemberLoad() prints both .a and .o names, so no need to
- // print the .a name here.
- if (config->printEachFile && magic != file_magic::archive)
+ // print the .a name here. Similarly skip lazy files.
+ if (config->printEachFile && magic != file_magic::archive && !isLazy)
message(toString(newFile));
inputFiles.insert(newFile);
}
return newFile;
}
+static std::vector<StringRef> missingAutolinkWarnings;
static void addLibrary(StringRef name, bool isNeeded, bool isWeak,
- bool isReexport, bool isExplicit, bool forceLoad) {
- if (Optional<StringRef> path = findLibrary(name)) {
+ bool isReexport, bool isHidden, bool isExplicit,
+ LoadType loadType, InputFile *originFile = nullptr) {
+ if (std::optional<StringRef> path = findLibrary(name)) {
if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
- addFile(*path, forceLoad, isExplicit))) {
+ addFile(*path, loadType, /*isLazy=*/false, isExplicit,
+ /*isBundleLoader=*/false, isHidden))) {
if (isNeeded)
dylibFile->forceNeeded = true;
if (isWeak)
}
return;
}
+ if (loadType == LoadType::LCLinkerOption) {
+ assert(originFile);
+ missingAutolinkWarnings.push_back(
+ saver().save(toString(originFile) +
+ ": auto-linked library not found for -l" + name));
+ return;
+ }
error("library not found for -l" + name);
}
+static DenseSet<StringRef> loadedObjectFrameworks;
static void addFramework(StringRef name, bool isNeeded, bool isWeak,
- bool isReexport, bool isExplicit) {
- if (Optional<std::string> path = findFramework(name)) {
- if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
- addFile(*path, /*forceLoadArchive=*/false, isExplicit))) {
+ bool isReexport, bool isExplicit, LoadType loadType,
+ InputFile *originFile = nullptr) {
+ if (std::optional<StringRef> path = findFramework(name)) {
+ if (loadedObjectFrameworks.contains(*path))
+ return;
+
+ InputFile *file =
+ addFile(*path, loadType, /*isLazy=*/false, isExplicit, false);
+ if (auto *dylibFile = dyn_cast_or_null<DylibFile>(file)) {
if (isNeeded)
dylibFile->forceNeeded = true;
if (isWeak)
config->hasReexports = true;
dylibFile->reexport = true;
}
+ } else if (isa_and_nonnull<ObjFile>(file) ||
+ isa_and_nonnull<BitcodeFile>(file)) {
+ // Cache frameworks containing object or bitcode files to avoid duplicate
+ // symbols. Frameworks containing static archives are cached separately
+ // in addFile() to share caching with libraries, and frameworks
+ // containing dylibs should allow overwriting of attributes such as
+ // forceNeeded by subsequent loads
+ loadedObjectFrameworks.insert(*path);
}
return;
}
+ if (loadType == LoadType::LCLinkerOption) {
+ assert(originFile);
+ missingAutolinkWarnings.push_back(saver().save(
+ toString(originFile) +
+ ": auto-linked framework not found for -framework " + name));
+ return;
+ }
error("framework not found for -framework " + name);
}
// Parses LC_LINKER_OPTION contents, which can add additional command line
-// flags.
+// flags. This directly parses the flags instead of using the standard argument
+// parser to improve performance.
void macho::parseLCLinkerOption(InputFile *f, unsigned argc, StringRef data) {
- SmallVector<const char *, 4> argv;
+ if (config->ignoreAutoLink)
+ return;
+
+ SmallVector<StringRef, 4> argv;
size_t offset = 0;
for (unsigned i = 0; i < argc && offset < data.size(); ++i) {
argv.push_back(data.data() + offset);
if (argv.size() != argc || offset > data.size())
fatal(toString(f) + ": invalid LC_LINKER_OPTION");
- MachOOptTable table;
- unsigned missingIndex, missingCount;
- InputArgList args = table.ParseArgs(argv, missingIndex, missingCount);
- if (missingCount)
- fatal(Twine(args.getArgString(missingIndex)) + ": missing argument");
- for (const Arg *arg : args.filtered(OPT_UNKNOWN))
- error("unknown argument: " + arg->getAsString(args));
-
- for (const Arg *arg : args) {
- switch (arg->getOption().getID()) {
- case OPT_l: {
- StringRef name = arg->getValue();
- bool forceLoad =
- config->forceLoadSwift ? name.startswith("swift") : false;
- addLibrary(name, /*isNeeded=*/false, /*isWeak=*/false,
- /*isReexport=*/false, /*isExplicit=*/false, forceLoad);
- break;
- }
- case OPT_framework:
- addFramework(arg->getValue(), /*isNeeded=*/false, /*isWeak=*/false,
- /*isReexport=*/false, /*isExplicit=*/false);
- break;
- default:
- error(arg->getSpelling() + " is not allowed in LC_LINKER_OPTION");
- }
+ unsigned i = 0;
+ StringRef arg = argv[i];
+ if (arg.consume_front("-l")) {
+ if (config->ignoreAutoLinkOptions.contains(arg))
+ return;
+ addLibrary(arg, /*isNeeded=*/false, /*isWeak=*/false,
+ /*isReexport=*/false, /*isHidden=*/false, /*isExplicit=*/false,
+ LoadType::LCLinkerOption, f);
+ } else if (arg == "-framework") {
+ StringRef name = argv[++i];
+ if (config->ignoreAutoLinkOptions.contains(name))
+ return;
+ addFramework(name, /*isNeeded=*/false, /*isWeak=*/false,
+ /*isReexport=*/false, /*isExplicit=*/false,
+ LoadType::LCLinkerOption, f);
+ } else {
+ error(arg + " is not allowed in LC_LINKER_OPTION");
}
}
-static void addFileList(StringRef path) {
- Optional<MemoryBufferRef> buffer = readFile(path);
+static void addFileList(StringRef path, bool isLazy) {
+ std::optional<MemoryBufferRef> buffer = readFile(path);
if (!buffer)
return;
MemoryBufferRef mbref = *buffer;
for (StringRef path : args::getLines(mbref))
- addFile(rerootPath(path), /*forceLoadArchive=*/false);
-}
-
-// An order file has one entry per line, in the following format:
-//
-// <cpu>:<object file>:<symbol name>
-//
-// <cpu> and <object file> are optional. If not specified, then that entry
-// matches any symbol of that name. Parsing this format is not quite
-// straightforward because the symbol name itself can contain colons, so when
-// encountering a colon, we consider the preceding characters to decide if it
-// can be a valid CPU type or file path.
-//
-// If a symbol is matched by multiple entries, then it takes the lowest-ordered
-// entry (the one nearest to the front of the list.)
-//
-// The file can also have line comments that start with '#'.
-static void parseOrderFile(StringRef path) {
- Optional<MemoryBufferRef> buffer = readFile(path);
- if (!buffer) {
- error("Could not read order file at " + path);
- return;
- }
-
- MemoryBufferRef mbref = *buffer;
- size_t priority = std::numeric_limits<size_t>::max();
- for (StringRef line : args::getLines(mbref)) {
- StringRef objectFile, symbol;
- line = line.take_until([](char c) { return c == '#'; }); // ignore comments
- line = line.ltrim();
-
- CPUType cpuType = StringSwitch<CPUType>(line)
- .StartsWith("i386:", CPU_TYPE_I386)
- .StartsWith("x86_64:", CPU_TYPE_X86_64)
- .StartsWith("arm:", CPU_TYPE_ARM)
- .StartsWith("arm64:", CPU_TYPE_ARM64)
- .StartsWith("ppc:", CPU_TYPE_POWERPC)
- .StartsWith("ppc64:", CPU_TYPE_POWERPC64)
- .Default(CPU_TYPE_ANY);
-
- if (cpuType != CPU_TYPE_ANY && cpuType != target->cpuType)
- continue;
-
- // Drop the CPU type as well as the colon
- if (cpuType != CPU_TYPE_ANY)
- line = line.drop_until([](char c) { return c == ':'; }).drop_front();
-
- constexpr std::array<StringRef, 2> fileEnds = {".o:", ".o):"};
- for (StringRef fileEnd : fileEnds) {
- size_t pos = line.find(fileEnd);
- if (pos != StringRef::npos) {
- // Split the string around the colon
- objectFile = line.take_front(pos + fileEnd.size() - 1);
- line = line.drop_front(pos + fileEnd.size());
- break;
- }
- }
- symbol = line.trim();
-
- if (!symbol.empty()) {
- SymbolPriorityEntry &entry = config->priorities[symbol];
- if (!objectFile.empty())
- entry.objectFiles.insert(std::make_pair(objectFile, priority));
- else
- entry.anyObjectFile = std::max(entry.anyObjectFile, priority);
- }
-
- --priority;
- }
+ addFile(rerootPath(path), LoadType::CommandLine, isLazy);
}
// We expect sub-library names of the form "libfoo", which will match a dylib
if (auto *dylibFile = dyn_cast<DylibFile>(file)) {
StringRef filename = path::filename(dylibFile->getName());
if (filename.consume_front(searchName) &&
- (filename.empty() ||
- find(extensions, filename) != extensions.end())) {
+ (filename.empty() || llvm::is_contained(extensions, filename))) {
dylibFile->reexport = true;
return true;
}
InitializeAllAsmParsers();
}
-static void compileBitcodeFiles() {
- // FIXME: Remove this once LTO.cpp honors config->exportDynamic.
- if (config->exportDynamic)
- for (InputFile *file : inputFiles)
- if (isa<BitcodeFile>(file)) {
- warn("the effect of -export_dynamic on LTO is not yet implemented");
- break;
- }
-
+static bool compileBitcodeFiles() {
TimeTraceScope timeScope("LTO");
auto *lto = make<BitcodeCompiler>();
for (InputFile *file : inputFiles)
if (auto *bitcodeFile = dyn_cast<BitcodeFile>(file))
- lto->add(*bitcodeFile);
+ if (!file->lazy)
+ lto->add(*bitcodeFile);
- for (ObjFile *file : lto->compile())
+ std::vector<ObjFile *> compiled = lto->compile();
+ for (ObjFile *file : compiled)
inputFiles.insert(file);
+
+ return !compiled.empty();
}
// Replaces common symbols with defined symbols residing in __common sections.
// but it's not really worth supporting the linking of 64-bit programs on
// 32-bit archs.
ArrayRef<uint8_t> data = {nullptr, static_cast<size_t>(common->size)};
- auto *isec = make<ConcatInputSection>(
- segment_names::data, section_names::common, common->getFile(), data,
- common->align, S_ZEROFILL);
+ // FIXME avoid creating one Section per symbol?
+ auto *section =
+ make<Section>(common->getFile(), segment_names::data,
+ section_names::common, S_ZEROFILL, /*addr=*/0);
+ auto *isec = make<ConcatInputSection>(*section, data, common->align);
if (!osec)
osec = ConcatOutputSection::getOrCreateForInput(isec);
isec->parent = osec;
// FIXME: CommonSymbol should store isReferencedDynamically, noDeadStrip
// and pass them on here.
- replaceSymbol<Defined>(sym, sym->getName(), isec->getFile(), isec,
- /*value=*/0,
- /*size=*/0,
- /*isWeakDef=*/false,
- /*isExternal=*/true, common->privateExtern,
- /*isThumb=*/false,
- /*isReferencedDynamically=*/false,
- /*noDeadStrip=*/false);
+ replaceSymbol<Defined>(
+ sym, sym->getName(), common->getFile(), isec, /*value=*/0, common->size,
+ /*isWeakDef=*/false, /*isExternal=*/true, common->privateExtern,
+ /*includeInSymtab=*/true, /*isThumb=*/false,
+ /*isReferencedDynamically=*/false, /*noDeadStrip=*/false);
}
}
section_names::objcCatList,
section_names::objcNonLazyCatList,
section_names::objcProtoList,
- section_names::objcImageInfo};
+ section_names::objCImageInfo};
for (StringRef s : v)
config->sectionRenameMap[{segment_names::data, s}] = {
segment_names::dataConst, s};
map_iterator(s.end(), toLowerDash));
}
-// Has the side-effect of setting Config::platformInfo.
-static PlatformKind parsePlatformVersion(const ArgList &args) {
- const Arg *arg = args.getLastArg(OPT_platform_version);
- if (!arg) {
- error("must specify -platform_version");
- return PlatformKind::unknown;
- }
+struct PlatformVersion {
+ PlatformType platform = PLATFORM_UNKNOWN;
+ llvm::VersionTuple minimum;
+ llvm::VersionTuple sdk;
+};
+static PlatformVersion parsePlatformVersion(const Arg *arg) {
+ assert(arg->getOption().getID() == OPT_platform_version);
StringRef platformStr = arg->getValue(0);
StringRef minVersionStr = arg->getValue(1);
StringRef sdkVersionStr = arg->getValue(2);
+ PlatformVersion platformVersion;
+
// TODO(compnerd) see if we can generate this case list via XMACROS
- PlatformKind platform =
- StringSwitch<PlatformKind>(lowerDash(platformStr))
- .Cases("macos", "1", PlatformKind::macOS)
- .Cases("ios", "2", PlatformKind::iOS)
- .Cases("tvos", "3", PlatformKind::tvOS)
- .Cases("watchos", "4", PlatformKind::watchOS)
- .Cases("bridgeos", "5", PlatformKind::bridgeOS)
- .Cases("mac-catalyst", "6", PlatformKind::macCatalyst)
- .Cases("ios-simulator", "7", PlatformKind::iOSSimulator)
- .Cases("tvos-simulator", "8", PlatformKind::tvOSSimulator)
- .Cases("watchos-simulator", "9", PlatformKind::watchOSSimulator)
- .Cases("driverkit", "10", PlatformKind::driverKit)
- .Default(PlatformKind::unknown);
- if (platform == PlatformKind::unknown)
+ platformVersion.platform =
+ StringSwitch<PlatformType>(lowerDash(platformStr))
+ .Cases("macos", "1", PLATFORM_MACOS)
+ .Cases("ios", "2", PLATFORM_IOS)
+ .Cases("tvos", "3", PLATFORM_TVOS)
+ .Cases("watchos", "4", PLATFORM_WATCHOS)
+ .Cases("bridgeos", "5", PLATFORM_BRIDGEOS)
+ .Cases("mac-catalyst", "6", PLATFORM_MACCATALYST)
+ .Cases("ios-simulator", "7", PLATFORM_IOSSIMULATOR)
+ .Cases("tvos-simulator", "8", PLATFORM_TVOSSIMULATOR)
+ .Cases("watchos-simulator", "9", PLATFORM_WATCHOSSIMULATOR)
+ .Cases("driverkit", "10", PLATFORM_DRIVERKIT)
+ .Default(PLATFORM_UNKNOWN);
+ if (platformVersion.platform == PLATFORM_UNKNOWN)
error(Twine("malformed platform: ") + platformStr);
// TODO: check validity of version strings, which varies by platform
// NOTE: ld64 accepts version strings with 5 components
// llvm::VersionTuple accepts no more than 4 components
// Has Apple ever published version strings with 5 components?
- if (config->platformInfo.minimum.tryParse(minVersionStr))
+ if (platformVersion.minimum.tryParse(minVersionStr))
error(Twine("malformed minimum version: ") + minVersionStr);
- if (config->platformInfo.sdk.tryParse(sdkVersionStr))
+ if (platformVersion.sdk.tryParse(sdkVersionStr))
error(Twine("malformed sdk version: ") + sdkVersionStr);
- return platform;
+ return platformVersion;
+}
+
+// Has the side-effect of setting Config::platformInfo.
+static PlatformType parsePlatformVersions(const ArgList &args) {
+ std::map<PlatformType, PlatformVersion> platformVersions;
+ const PlatformVersion *lastVersionInfo = nullptr;
+ for (const Arg *arg : args.filtered(OPT_platform_version)) {
+ PlatformVersion version = parsePlatformVersion(arg);
+
+ // For each platform, the last flag wins:
+ // `-platform_version macos 2 3 -platform_version macos 4 5` has the same
+ // effect as just passing `-platform_version macos 4 5`.
+ // FIXME: ld64 warns on multiple flags for one platform. Should we?
+ platformVersions[version.platform] = version;
+ lastVersionInfo = &platformVersions[version.platform];
+ }
+
+ if (platformVersions.empty()) {
+ error("must specify -platform_version");
+ return PLATFORM_UNKNOWN;
+ }
+ if (platformVersions.size() > 2) {
+ error("must specify -platform_version at most twice");
+ return PLATFORM_UNKNOWN;
+ }
+ if (platformVersions.size() == 2) {
+ bool isZipperedCatalyst = platformVersions.count(PLATFORM_MACOS) &&
+ platformVersions.count(PLATFORM_MACCATALYST);
+
+ if (!isZipperedCatalyst) {
+ error("lld supports writing zippered outputs only for "
+ "macos and mac-catalyst");
+ } else if (config->outputType != MH_DYLIB &&
+ config->outputType != MH_BUNDLE) {
+ error("writing zippered outputs only valid for -dylib and -bundle");
+ } else {
+ config->platformInfo.minimum = platformVersions[PLATFORM_MACOS].minimum;
+ config->platformInfo.sdk = platformVersions[PLATFORM_MACOS].sdk;
+ config->secondaryPlatformInfo = PlatformInfo{};
+ config->secondaryPlatformInfo->minimum =
+ platformVersions[PLATFORM_MACCATALYST].minimum;
+ config->secondaryPlatformInfo->sdk =
+ platformVersions[PLATFORM_MACCATALYST].sdk;
+ }
+ return PLATFORM_MACOS;
+ }
+
+ config->platformInfo.minimum = lastVersionInfo->minimum;
+ config->platformInfo.sdk = lastVersionInfo->sdk;
+ return lastVersionInfo->platform;
}
// Has the side-effect of setting Config::target.
static TargetInfo *createTargetInfo(InputArgList &args) {
StringRef archName = args.getLastArgValue(OPT_arch);
- if (archName.empty())
- fatal("must specify -arch");
- PlatformKind platform = parsePlatformVersion(args);
+ if (archName.empty()) {
+ error("must specify -arch");
+ return nullptr;
+ }
+ PlatformType platform = parsePlatformVersions(args);
config->platformInfo.target =
MachO::Target(getArchitectureFromName(archName), platform);
+ if (config->secondaryPlatformInfo) {
+ config->secondaryPlatformInfo->target =
+ MachO::Target(getArchitectureFromName(archName), PLATFORM_MACCATALYST);
+ }
- uint32_t cpuType;
- uint32_t cpuSubtype;
- std::tie(cpuType, cpuSubtype) = getCPUTypeFromArchitecture(config->arch());
-
+ auto [cpuType, cpuSubtype] = getCPUTypeFromArchitecture(config->arch());
switch (cpuType) {
case CPU_TYPE_X86_64:
return createX86_64TargetInfo();
case CPU_TYPE_ARM:
return createARMTargetInfo(cpuSubtype);
default:
- fatal("missing or unsupported -arch " + archName);
+ error("missing or unsupported -arch " + archName);
+ return nullptr;
}
}
(treatment == UndefinedSymbolTreatment::warning ||
treatment == UndefinedSymbolTreatment::suppress)) {
if (treatment == UndefinedSymbolTreatment::warning)
- error("'-undefined warning' only valid with '-flat_namespace'");
+ fatal("'-undefined warning' only valid with '-flat_namespace'");
else
- error("'-undefined suppress' only valid with '-flat_namespace'");
+ fatal("'-undefined suppress' only valid with '-flat_namespace'");
treatment = UndefinedSymbolTreatment::error;
}
return treatment;
}
static ICFLevel getICFLevel(const ArgList &args) {
- bool noDeduplicate = args.hasArg(OPT_no_deduplicate);
StringRef icfLevelStr = args.getLastArgValue(OPT_icf_eq);
auto icfLevel = StringSwitch<ICFLevel>(icfLevelStr)
.Cases("none", "", ICFLevel::none)
warn(Twine("unknown --icf=OPTION `") + icfLevelStr +
"', defaulting to `none'");
icfLevel = ICFLevel::none;
- } else if (icfLevel != ICFLevel::none && noDeduplicate) {
- warn(Twine("`--icf=" + icfLevelStr +
- "' conflicts with -no_deduplicate, setting to `none'"));
- icfLevel = ICFLevel::none;
- } else if (icfLevel == ICFLevel::safe) {
- warn(Twine("`--icf=safe' is not yet implemented, reverting to `none'"));
- icfLevel = ICFLevel::none;
}
return icfLevel;
}
+static ObjCStubsMode getObjCStubsMode(const ArgList &args) {
+ const Arg *arg = args.getLastArg(OPT_objc_stubs_fast, OPT_objc_stubs_small);
+ if (!arg)
+ return ObjCStubsMode::fast;
+
+ if (arg->getOption().getID() == OPT_objc_stubs_small)
+ warn("-objc_stubs_small is not yet implemented, defaulting to "
+ "-objc_stubs_fast");
+ return ObjCStubsMode::fast;
+}
+
static void warnIfDeprecatedOption(const Option &opt) {
if (!opt.getGroup().isValid())
return;
case OPT_grp_ignored:
warn("Option `" + opt.getPrefixedName() + "' is ignored.");
break;
+ case OPT_grp_ignored_silently:
+ break;
default:
warn("Option `" + opt.getPrefixedName() +
"' is not yet implemented. Stay tuned...");
return getenv("LLD_REPRODUCE");
}
+// Parse options of the form "old;new".
+static std::pair<StringRef, StringRef> getOldNewOptions(opt::InputArgList &args,
+ unsigned id) {
+ auto *arg = args.getLastArg(id);
+ if (!arg)
+ return {"", ""};
+
+ StringRef s = arg->getValue();
+ std::pair<StringRef, StringRef> ret = s.split(';');
+ if (ret.second.empty())
+ error(arg->getSpelling() + " expects 'old;new' format, but got " + s);
+ return ret;
+}
+
static void parseClangOption(StringRef opt, const Twine &msg) {
std::string err;
raw_string_ostream os(err);
return sectAligns;
}
-PlatformKind macho::removeSimulator(PlatformKind platform) {
+PlatformType macho::removeSimulator(PlatformType platform) {
switch (platform) {
- case PlatformKind::iOSSimulator:
- return PlatformKind::iOS;
- case PlatformKind::tvOSSimulator:
- return PlatformKind::tvOS;
- case PlatformKind::watchOSSimulator:
- return PlatformKind::watchOS;
+ case PLATFORM_IOSSIMULATOR:
+ return PLATFORM_IOS;
+ case PLATFORM_TVOSSIMULATOR:
+ return PLATFORM_TVOS;
+ case PLATFORM_WATCHOSSIMULATOR:
+ return PLATFORM_WATCHOS;
default:
return platform;
}
}
+static bool supportsNoPie() {
+ return !(config->arch() == AK_arm64 || config->arch() == AK_arm64e ||
+ config->arch() == AK_arm64_32);
+}
+
+static bool shouldAdhocSignByDefault(Architecture arch, PlatformType platform) {
+ if (arch != AK_arm64 && arch != AK_arm64e)
+ return false;
+
+ return platform == PLATFORM_MACOS || platform == PLATFORM_IOSSIMULATOR ||
+ platform == PLATFORM_TVOSSIMULATOR ||
+ platform == PLATFORM_WATCHOSSIMULATOR;
+}
+
static bool dataConstDefault(const InputArgList &args) {
- static const std::vector<std::pair<PlatformKind, VersionTuple>> minVersion = {
- {PlatformKind::macOS, VersionTuple(10, 15)},
- {PlatformKind::iOS, VersionTuple(13, 0)},
- {PlatformKind::tvOS, VersionTuple(13, 0)},
- {PlatformKind::watchOS, VersionTuple(6, 0)},
- {PlatformKind::bridgeOS, VersionTuple(4, 0)}};
- PlatformKind platform = removeSimulator(config->platformInfo.target.Platform);
+ static const std::array<std::pair<PlatformType, VersionTuple>, 5> minVersion =
+ {{{PLATFORM_MACOS, VersionTuple(10, 15)},
+ {PLATFORM_IOS, VersionTuple(13, 0)},
+ {PLATFORM_TVOS, VersionTuple(13, 0)},
+ {PLATFORM_WATCHOS, VersionTuple(6, 0)},
+ {PLATFORM_BRIDGEOS, VersionTuple(4, 0)}}};
+ PlatformType platform = removeSimulator(config->platformInfo.target.Platform);
auto it = llvm::find_if(minVersion,
[&](const auto &p) { return p.first == platform; });
if (it != minVersion.end())
switch (config->outputType) {
case MH_EXECUTE:
- return !args.hasArg(OPT_no_pie);
+ return !(args.hasArg(OPT_no_pie) && supportsNoPie());
case MH_BUNDLE:
// FIXME: return false when -final_name ...
// has prefix "/System/Library/UserEventPlugins/"
return false;
}
+static bool shouldEmitChainedFixups(const InputArgList &args) {
+ const Arg *arg = args.getLastArg(OPT_fixup_chains, OPT_no_fixup_chains);
+ if (arg && arg->getOption().matches(OPT_no_fixup_chains))
+ return false;
+
+ bool isRequested = arg != nullptr;
+
+ // Version numbers taken from the Xcode 13.3 release notes.
+ static const std::array<std::pair<PlatformType, VersionTuple>, 4> minVersion =
+ {{{PLATFORM_MACOS, VersionTuple(11, 0)},
+ {PLATFORM_IOS, VersionTuple(13, 4)},
+ {PLATFORM_TVOS, VersionTuple(14, 0)},
+ {PLATFORM_WATCHOS, VersionTuple(7, 0)}}};
+ PlatformType platform = removeSimulator(config->platformInfo.target.Platform);
+ auto it = llvm::find_if(minVersion,
+ [&](const auto &p) { return p.first == platform; });
+ if (it != minVersion.end() && it->second > config->platformInfo.minimum) {
+ if (!isRequested)
+ return false;
+
+ warn("-fixup_chains requires " + getPlatformName(config->platform()) + " " +
+ it->second.getAsString() + ", which is newer than target minimum of " +
+ config->platformInfo.minimum.getAsString());
+ }
+
+ if (!is_contained({AK_x86_64, AK_x86_64h, AK_arm64}, config->arch())) {
+ if (isRequested)
+ error("-fixup_chains is only supported on x86_64 and arm64 targets");
+ return false;
+ }
+
+ if (!config->isPic) {
+ if (isRequested)
+ error("-fixup_chains is incompatible with -no_pie");
+ return false;
+ }
+
+ // TODO: Enable by default once stable.
+ return isRequested;
+}
+
void SymbolPatterns::clear() {
literals.clear();
globs.clear();
return matchLiteral(symbolName) || matchGlob(symbolName);
}
+static void parseSymbolPatternsFile(const Arg *arg,
+ SymbolPatterns &symbolPatterns) {
+ StringRef path = arg->getValue();
+ std::optional<MemoryBufferRef> buffer = readFile(path);
+ if (!buffer) {
+ error("Could not read symbol file: " + path);
+ return;
+ }
+ MemoryBufferRef mbref = *buffer;
+ for (StringRef line : args::getLines(mbref)) {
+ line = line.take_until([](char c) { return c == '#'; }).trim();
+ if (!line.empty())
+ symbolPatterns.insert(line);
+ }
+}
+
static void handleSymbolPatterns(InputArgList &args,
SymbolPatterns &symbolPatterns,
unsigned singleOptionCode,
unsigned listFileOptionCode) {
for (const Arg *arg : args.filtered(singleOptionCode))
symbolPatterns.insert(arg->getValue());
- for (const Arg *arg : args.filtered(listFileOptionCode)) {
- StringRef path = arg->getValue();
- Optional<MemoryBufferRef> buffer = readFile(path);
- if (!buffer) {
- error("Could not read symbol file: " + path);
- continue;
- }
- MemoryBufferRef mbref = *buffer;
- for (StringRef line : args::getLines(mbref)) {
- line = line.take_until([](char c) { return c == '#'; }).trim();
- if (!line.empty())
- symbolPatterns.insert(line);
- }
- }
+ for (const Arg *arg : args.filtered(listFileOptionCode))
+ parseSymbolPatternsFile(arg, symbolPatterns);
}
-void createFiles(const InputArgList &args) {
+static void createFiles(const InputArgList &args) {
TimeTraceScope timeScope("Load input files");
// This loop should be reserved for options whose exact ordering matters.
// Other options should be handled via filtered() and/or getLastArg().
+ bool isLazy = false;
for (const Arg *arg : args) {
const Option &opt = arg->getOption();
warnIfDeprecatedOption(opt);
switch (opt.getID()) {
case OPT_INPUT:
- addFile(rerootPath(arg->getValue()), /*forceLoadArchive=*/false);
+ addFile(rerootPath(arg->getValue()), LoadType::CommandLine, isLazy);
break;
case OPT_needed_library:
if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
- addFile(rerootPath(arg->getValue()), false)))
+ addFile(rerootPath(arg->getValue()), LoadType::CommandLine)))
dylibFile->forceNeeded = true;
break;
case OPT_reexport_library:
- if (auto *dylibFile = dyn_cast_or_null<DylibFile>(addFile(
- rerootPath(arg->getValue()), /*forceLoadArchive=*/false))) {
+ if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
+ addFile(rerootPath(arg->getValue()), LoadType::CommandLine))) {
config->hasReexports = true;
dylibFile->reexport = true;
}
break;
case OPT_weak_library:
if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
- addFile(rerootPath(arg->getValue()), /*forceLoadArchive=*/false)))
+ addFile(rerootPath(arg->getValue()), LoadType::CommandLine)))
dylibFile->forceWeakImport = true;
break;
case OPT_filelist:
- addFileList(arg->getValue());
+ addFileList(arg->getValue(), isLazy);
break;
case OPT_force_load:
- addFile(rerootPath(arg->getValue()), /*forceLoadArchive=*/true);
+ addFile(rerootPath(arg->getValue()), LoadType::CommandLineForce);
+ break;
+ case OPT_load_hidden:
+ addFile(rerootPath(arg->getValue()), LoadType::CommandLine,
+ /*isLazy=*/false, /*isExplicit=*/true, /*isBundleLoader=*/false,
+ /*isForceHidden=*/true);
break;
case OPT_l:
case OPT_needed_l:
case OPT_reexport_l:
case OPT_weak_l:
+ case OPT_hidden_l:
addLibrary(arg->getValue(), opt.getID() == OPT_needed_l,
opt.getID() == OPT_weak_l, opt.getID() == OPT_reexport_l,
- /*isExplicit=*/true, /*forceLoad=*/false);
+ opt.getID() == OPT_hidden_l,
+ /*isExplicit=*/true, LoadType::CommandLine);
break;
case OPT_framework:
case OPT_needed_framework:
case OPT_weak_framework:
addFramework(arg->getValue(), opt.getID() == OPT_needed_framework,
opt.getID() == OPT_weak_framework,
- opt.getID() == OPT_reexport_framework, /*isExplicit=*/true);
+ opt.getID() == OPT_reexport_framework, /*isExplicit=*/true,
+ LoadType::CommandLine);
+ break;
+ case OPT_start_lib:
+ if (isLazy)
+ error("nested --start-lib");
+ isLazy = true;
+ break;
+ case OPT_end_lib:
+ if (!isLazy)
+ error("stray --end-lib");
+ isLazy = false;
break;
default:
break;
TimeTraceScope timeScope("Gathering input sections");
int inputOrder = 0;
for (const InputFile *file : inputFiles) {
- for (const SubsectionMap &map : file->subsections) {
+ for (const Section *section : file->sections) {
+ // Compact unwind entries require special handling elsewhere. (In
+ // contrast, EH frames are handled like regular ConcatInputSections.)
+ if (section->name == section_names::compactUnwind)
+ continue;
ConcatOutputSection *osec = nullptr;
- for (const SubsectionEntry &entry : map) {
- if (auto *isec = dyn_cast<ConcatInputSection>(entry.isec)) {
+ for (const Subsection &subsection : section->subsections) {
+ if (auto *isec = dyn_cast<ConcatInputSection>(subsection.isec)) {
if (isec->isCoalescedWeak())
continue;
- if (isec->getSegName() == segment_names::ld) {
- assert(isec->getName() == section_names::compactUnwind);
- in.unwindInfo->addInput(isec);
+ if (config->emitInitOffsets &&
+ sectionType(isec->getFlags()) == S_MOD_INIT_FUNC_POINTERS) {
+ in.initOffsets->addInput(isec);
continue;
}
isec->outSecOff = inputOrder++;
osec = ConcatOutputSection::getOrCreateForInput(isec);
isec->parent = osec;
inputSections.push_back(isec);
- } else if (auto *isec = dyn_cast<CStringInputSection>(entry.isec)) {
- if (in.cStringSection->inputOrder == UnspecifiedInputOrder)
- in.cStringSection->inputOrder = inputOrder++;
- in.cStringSection->addInput(isec);
- } else if (auto *isec = dyn_cast<WordLiteralInputSection>(entry.isec)) {
+ } else if (auto *isec =
+ dyn_cast<CStringInputSection>(subsection.isec)) {
+ if (isec->getName() == section_names::objcMethname) {
+ if (in.objcMethnameSection->inputOrder == UnspecifiedInputOrder)
+ in.objcMethnameSection->inputOrder = inputOrder++;
+ in.objcMethnameSection->addInput(isec);
+ } else {
+ if (in.cStringSection->inputOrder == UnspecifiedInputOrder)
+ in.cStringSection->inputOrder = inputOrder++;
+ in.cStringSection->addInput(isec);
+ }
+ } else if (auto *isec =
+ dyn_cast<WordLiteralInputSection>(subsection.isec)) {
if (in.wordLiteralSection->inputOrder == UnspecifiedInputOrder)
in.wordLiteralSection->inputOrder = inputOrder++;
in.wordLiteralSection->addInput(isec);
}
}
}
+ if (!file->objCImageInfo.empty())
+ in.objCImageInfo->addFile(file);
}
assert(inputOrder <= UnspecifiedInputOrder);
}
static void foldIdenticalLiterals() {
+ TimeTraceScope timeScope("Fold identical literals");
// We always create a cStringSection, regardless of whether dedupLiterals is
// true. If it isn't, we simply create a non-deduplicating CStringSection.
// Either way, we must unconditionally finalize it here.
in.cStringSection->finalizeContents();
- if (in.wordLiteralSection)
- in.wordLiteralSection->finalizeContents();
+ in.objcMethnameSection->finalizeContents();
+ in.wordLiteralSection->finalizeContents();
+}
+
+static void addSynthenticMethnames() {
+ std::string &data = *make<std::string>();
+ llvm::raw_string_ostream os(data);
+ const int prefixLength = ObjCStubsSection::symbolPrefix.size();
+ for (Symbol *sym : symtab->getSymbols())
+ if (isa<Undefined>(sym))
+ if (sym->getName().startswith(ObjCStubsSection::symbolPrefix))
+ os << sym->getName().drop_front(prefixLength) << '\0';
+
+ if (data.empty())
+ return;
+
+ const auto *buf = reinterpret_cast<const uint8_t *>(data.c_str());
+ Section §ion = *make<Section>(/*file=*/nullptr, segment_names::text,
+ section_names::objcMethname,
+ S_CSTRING_LITERALS, /*addr=*/0);
+
+ auto *isec =
+ make<CStringInputSection>(section, ArrayRef<uint8_t>{buf, data.size()},
+ /*align=*/1, /*dedupLiterals=*/true);
+ isec->splitIntoPieces();
+ for (auto &piece : isec->pieces)
+ piece.live = true;
+ section.subsections.push_back({0, isec});
+ in.objcMethnameSection->addInput(isec);
+ in.objcMethnameSection->isec->markLive(0);
}
static void referenceStubBinder() {
// dyld_stub_binder is in libSystem.dylib, which is usually linked in. This
// isn't needed for correctness, but the presence of that symbol suppresses
// "no symbols" diagnostics from `nm`.
- // StubHelperSection::setup() adds a reference and errors out if
+ // StubHelperSection::setUp() adds a reference and errors out if
// dyld_stub_binder doesn't exist in case it is actually needed.
symtab->addUndefined("dyld_stub_binder", /*file=*/nullptr, /*isWeak=*/false);
}
-bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
- raw_ostream &stdoutOS, raw_ostream &stderrOS) {
- lld::stdoutOS = &stdoutOS;
- lld::stderrOS = &stderrOS;
+static void createAliases() {
+ for (const auto &pair : config->aliasedSymbols) {
+ if (const auto &sym = symtab->find(pair.first)) {
+ if (const auto &defined = dyn_cast<Defined>(sym)) {
+ symtab->aliasDefined(defined, pair.second, defined->getFile())
+ ->noDeadStrip = true;
+ } else {
+ error("TODO: support aliasing to symbols of kind " +
+ Twine(sym->kind()));
+ }
+ } else {
+ warn("undefined base symbol '" + pair.first + "' for alias '" +
+ pair.second + "'\n");
+ }
+ }
+
+ for (const InputFile *file : inputFiles) {
+ if (auto *objFile = dyn_cast<ObjFile>(file)) {
+ for (const AliasSymbol *alias : objFile->aliases) {
+ if (const auto &aliased = symtab->find(alias->getAliasedName())) {
+ if (const auto &defined = dyn_cast<Defined>(aliased)) {
+ symtab->aliasDefined(defined, alias->getName(), alias->getFile(),
+ alias->privateExtern);
+ } else {
+ // Common, dylib, and undefined symbols are all valid alias
+ // referents (undefineds can become valid Defined symbols later on
+ // in the link.)
+ error("TODO: support aliasing to symbols of kind " +
+ Twine(aliased->kind()));
+ }
+ } else {
+ // This shouldn't happen since MC generates undefined symbols to
+ // represent the alias referents. Thus we fatal() instead of just
+ // warning here.
+ fatal("unable to find alias referent " + alias->getAliasedName() +
+ " for " + alias->getName());
+ }
+ }
+ }
+ }
+}
+
+static void handleExplicitExports() {
+ if (config->hasExplicitExports) {
+ parallelForEach(symtab->getSymbols(), [](Symbol *sym) {
+ if (auto *defined = dyn_cast<Defined>(sym)) {
+ StringRef symbolName = defined->getName();
+ if (config->exportedSymbols.match(symbolName)) {
+ if (defined->privateExtern) {
+ if (defined->weakDefCanBeHidden) {
+ // weak_def_can_be_hidden symbols behave similarly to
+ // private_extern symbols in most cases, except for when
+ // it is explicitly exported.
+ // The former can be exported but the latter cannot.
+ defined->privateExtern = false;
+ } else {
+ warn("cannot export hidden symbol " + toString(*defined) +
+ "\n>>> defined in " + toString(defined->getFile()));
+ }
+ }
+ } else {
+ defined->privateExtern = true;
+ }
+ }
+ });
+ } else if (!config->unexportedSymbols.empty()) {
+ parallelForEach(symtab->getSymbols(), [](Symbol *sym) {
+ if (auto *defined = dyn_cast<Defined>(sym))
+ if (config->unexportedSymbols.match(defined->getName()))
+ defined->privateExtern = true;
+ });
+ }
+}
- errorHandler().cleanupCallback = []() { freeArena(); };
+bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly,
+ bool disableOutput) {
+ // This driver-specific context will be freed later by lldMain().
+ auto *ctx = new CommonLinkerContext;
+
+ ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
+ ctx->e.cleanupCallback = []() {
+ resolvedFrameworks.clear();
+ resolvedLibraries.clear();
+ cachedReads.clear();
+ concatOutputSections.clear();
+ inputFiles.clear();
+ inputSections.clear();
+ loadedArchives.clear();
+ loadedObjectFrameworks.clear();
+ missingAutolinkWarnings.clear();
+ syntheticSections.clear();
+ thunkMap.clear();
+
+ firstTLVDataSection = nullptr;
+ tar = nullptr;
+ memset(&in, 0, sizeof(in));
+
+ resetLoadedDylibs();
+ resetOutputSegments();
+ resetWriter();
+ InputFile::resetIdCount();
+ };
- errorHandler().logName = args::getFilenameWithoutExe(argsArr[0]);
- stderrOS.enable_colors(stderrOS.has_colors());
+ ctx->e.logName = args::getFilenameWithoutExe(argsArr[0]);
MachOOptTable parser;
InputArgList args = parser.parse(argsArr.slice(1));
- errorHandler().errorLimitExceededMsg =
- "too many errors emitted, stopping now "
- "(use --error-limit=0 to see all errors)";
- errorHandler().errorLimit = args::getInteger(args, OPT_error_limit_eq, 20);
- errorHandler().verbose = args.hasArg(OPT_verbose);
+ ctx->e.errorLimitExceededMsg = "too many errors emitted, stopping now "
+ "(use --error-limit=0 to see all errors)";
+ ctx->e.errorLimit = args::getInteger(args, OPT_error_limit_eq, 20);
+ ctx->e.verbose = args.hasArg(OPT_verbose);
if (args.hasArg(OPT_help_hidden)) {
parser.printHelp(argsArr[0], /*showHidden=*/true);
return true;
}
- config = make<Configuration>();
- symtab = make<SymbolTable>();
+ config = std::make_unique<Configuration>();
+ symtab = std::make_unique<SymbolTable>();
+ config->outputType = getOutputType(args);
target = createTargetInfo(args);
- depTracker =
- make<DependencyTracker>(args.getLastArgValue(OPT_dependency_info));
+ depTracker = std::make_unique<DependencyTracker>(
+ args.getLastArgValue(OPT_dependency_info));
+ if (errorCount())
+ return false;
+
+ if (args.hasArg(OPT_pagezero_size)) {
+ uint64_t pagezeroSize = args::getHex(args, OPT_pagezero_size, 0);
+
+ // ld64 does something really weird. It attempts to realign the value to the
+ // page size, but assumes the page size is 4K. This doesn't work with most
+ // of Apple's ARM64 devices, which use a page size of 16K. This means that
+ // it will first 4K align it by rounding down, then round up to 16K. This
+ // probably only happened because no one using this arg with anything other
+ // then 0, so no one checked if it did what is what it says it does.
+
+ // So we are not copying this weird behavior and doing the it in a logical
+ // way, by always rounding down to page size.
+ if (!isAligned(Align(target->getPageSize()), pagezeroSize)) {
+ pagezeroSize -= pagezeroSize % target->getPageSize();
+ warn("__PAGEZERO size is not page aligned, rounding down to 0x" +
+ Twine::utohexstr(pagezeroSize));
+ }
+
+ target->pageZeroSize = pagezeroSize;
+ }
+
+ config->osoPrefix = args.getLastArgValue(OPT_oso_prefix);
+ if (!config->osoPrefix.empty()) {
+ // Expand special characters, such as ".", "..", or "~", if present.
+ // Note: LD64 only expands "." and not other special characters.
+ // That seems silly to imitate so we will not try to follow it, but rather
+ // just use real_path() to do it.
+
+ // The max path length is 4096, in theory. However that seems quite long
+ // and seems unlikely that any one would want to strip everything from the
+ // path. Hence we've picked a reasonably large number here.
+ SmallString<1024> expanded;
+ if (!fs::real_path(config->osoPrefix, expanded,
+ /*expand_tilde=*/true)) {
+ // Note: LD64 expands "." to be `<current_dir>/`
+ // (ie., it has a slash suffix) whereas real_path() doesn't.
+ // So we have to append '/' to be consistent.
+ StringRef sep = sys::path::get_separator();
+ // real_path removes trailing slashes as part of the normalization, but
+ // these are meaningful for our text based stripping
+ if (config->osoPrefix.equals(".") || config->osoPrefix.endswith(sep))
+ expanded += sep;
+ config->osoPrefix = saver().save(expanded.str());
+ }
+ }
+
+ bool pie = args.hasFlag(OPT_pie, OPT_no_pie, true);
+ if (!supportsNoPie() && !pie) {
+ warn("-no_pie ignored for arm64");
+ pie = true;
+ }
+
+ config->isPic = config->outputType == MH_DYLIB ||
+ config->outputType == MH_BUNDLE ||
+ (config->outputType == MH_EXECUTE && pie);
// Must be set before any InputSections and Symbols are created.
config->deadStrip = args.hasArg(OPT_dead_strip);
args.hasArg(OPT_print_dylib_search) || getenv("RC_TRACE_DYLIB_SEARCHING");
config->printEachFile = args.hasArg(OPT_t);
config->printWhyLoad = args.hasArg(OPT_why_load);
- config->outputType = getOutputType(args);
+ config->omitDebugInfo = args.hasArg(OPT_S);
+ config->errorForArchMismatch = args.hasArg(OPT_arch_errors_fatal);
if (const Arg *arg = args.getLastArg(OPT_bundle_loader)) {
if (config->outputType != MH_BUNDLE)
error("-bundle_loader can only be used with MachO bundle output");
- addFile(arg->getValue(), /*forceLoadArchive=*/false, /*isExplicit=*/false,
- /*isBundleLoader=*/true);
+ addFile(arg->getValue(), LoadType::CommandLine, /*isLazy=*/false,
+ /*isExplicit=*/false, /*isBundleLoader=*/true);
}
+ for (auto *arg : args.filtered(OPT_dyld_env)) {
+ StringRef envPair(arg->getValue());
+ if (!envPair.contains('='))
+ error("-dyld_env's argument is malformed. Expected "
+ "-dyld_env <ENV_VAR>=<VALUE>, got `" +
+ envPair + "`");
+ config->dyldEnvs.push_back(envPair);
+ }
+ if (!config->dyldEnvs.empty() && config->outputType != MH_EXECUTE)
+ error("-dyld_env can only be used when creating executable output");
+
if (const Arg *arg = args.getLastArg(OPT_umbrella)) {
if (config->outputType != MH_DYLIB)
warn("-umbrella used, but not creating dylib");
config->umbrella = arg->getValue();
}
config->ltoObjPath = args.getLastArgValue(OPT_object_path_lto);
- config->ltoNewPassManager =
- args.hasFlag(OPT_no_lto_legacy_pass_manager, OPT_lto_legacy_pass_manager,
- LLVM_ENABLE_NEW_PASS_MANAGER);
config->ltoo = args::getInteger(args, OPT_lto_O, 2);
if (config->ltoo > 3)
error("--lto-O: invalid optimization level: " + Twine(config->ltoo));
config->thinLTOCacheDir = args.getLastArgValue(OPT_cache_path_lto);
config->thinLTOCachePolicy = getLTOCachePolicy(args);
+ config->thinLTOEmitImportsFiles = args.hasArg(OPT_thinlto_emit_imports_files);
+ config->thinLTOEmitIndexFiles = args.hasArg(OPT_thinlto_emit_index_files) ||
+ args.hasArg(OPT_thinlto_index_only) ||
+ args.hasArg(OPT_thinlto_index_only_eq);
+ config->thinLTOIndexOnly = args.hasArg(OPT_thinlto_index_only) ||
+ args.hasArg(OPT_thinlto_index_only_eq);
+ config->thinLTOIndexOnlyArg = args.getLastArgValue(OPT_thinlto_index_only_eq);
+ config->thinLTOObjectSuffixReplace =
+ getOldNewOptions(args, OPT_thinlto_object_suffix_replace_eq);
+ config->thinLTOPrefixReplace =
+ getOldNewOptions(args, OPT_thinlto_prefix_replace_eq);
+ if (config->thinLTOEmitIndexFiles && !config->thinLTOIndexOnly) {
+ if (args.hasArg(OPT_thinlto_object_suffix_replace_eq))
+ error("--thinlto-object-suffix-replace is not supported with "
+ "--thinlto-emit-index-files");
+ else if (args.hasArg(OPT_thinlto_prefix_replace_eq))
+ error("--thinlto-prefix-replace is not supported with "
+ "--thinlto-emit-index-files");
+ }
config->runtimePaths = args::getStrings(args, OPT_rpath);
- config->allLoad = args.hasArg(OPT_all_load);
+ config->allLoad = args.hasFlag(OPT_all_load, OPT_noall_load, false);
config->archMultiple = args.hasArg(OPT_arch_multiple);
config->applicationExtension = args.hasFlag(
OPT_application_extension, OPT_no_application_extension, false);
config->emitBitcodeBundle = args.hasArg(OPT_bitcode_bundle);
config->emitDataInCodeInfo =
args.hasFlag(OPT_data_in_code_info, OPT_no_data_in_code_info, true);
+ config->emitChainedFixups = shouldEmitChainedFixups(args);
+ config->emitInitOffsets =
+ config->emitChainedFixups || args.hasArg(OPT_init_offsets);
config->icfLevel = getICFLevel(args);
- config->dedupLiterals = args.hasArg(OPT_deduplicate_literals) ||
- config->icfLevel != ICFLevel::none;
+ config->dedupStrings =
+ args.hasFlag(OPT_deduplicate_strings, OPT_no_deduplicate_strings, true);
+ config->deadStripDuplicates = args.hasArg(OPT_dead_strip_duplicates);
+ config->warnDylibInstallName = args.hasFlag(
+ OPT_warn_dylib_install_name, OPT_no_warn_dylib_install_name, false);
+ config->ignoreOptimizationHints = args.hasArg(OPT_ignore_optimization_hints);
+ config->callGraphProfileSort = args.hasFlag(
+ OPT_call_graph_profile_sort, OPT_no_call_graph_profile_sort, true);
+ config->printSymbolOrder = args.getLastArgValue(OPT_print_symbol_order_eq);
+ config->forceExactCpuSubtypeMatch =
+ getenv("LD_DYLIB_CPU_SUBTYPES_MUST_MATCH");
+ config->objcStubsMode = getObjCStubsMode(args);
+ config->ignoreAutoLink = args.hasArg(OPT_ignore_auto_link);
+ for (const Arg *arg : args.filtered(OPT_ignore_auto_link_option))
+ config->ignoreAutoLinkOptions.insert(arg->getValue());
+ config->strictAutoLink = args.hasArg(OPT_strict_auto_link);
+
+ for (const Arg *arg : args.filtered(OPT_alias)) {
+ config->aliasedSymbols.push_back(
+ std::make_pair(arg->getValue(0), arg->getValue(1)));
+ }
// FIXME: Add a commandline flag for this too.
- config->zeroModTime = getenv("ZERO_AR_DATE");
+ if (const char *zero = getenv("ZERO_AR_DATE"))
+ config->zeroModTime = strcmp(zero, "0") != 0;
- std::array<PlatformKind, 3> encryptablePlatforms{
- PlatformKind::iOS, PlatformKind::watchOS, PlatformKind::tvOS};
+ std::array<PlatformType, 3> encryptablePlatforms{
+ PLATFORM_IOS, PLATFORM_WATCHOS, PLATFORM_TVOS};
config->emitEncryptionInfo =
args.hasFlag(OPT_encryptable, OPT_no_encryption,
is_contained(encryptablePlatforms, config->platform()));
#endif
if (const Arg *arg = args.getLastArg(OPT_install_name)) {
- if (config->outputType != MH_DYLIB)
- warn(arg->getAsString(args) + ": ignored, only has effect with -dylib");
+ if (config->warnDylibInstallName && config->outputType != MH_DYLIB)
+ warn(
+ arg->getAsString(args) +
+ ": ignored, only has effect with -dylib [--warn-dylib-install-name]");
else
config->installName = arg->getValue();
} else if (config->outputType == MH_DYLIB) {
config->segmentProtections.push_back({segName, maxProt, initProt});
}
+ config->hasExplicitExports =
+ args.hasArg(OPT_no_exported_symbols) ||
+ args.hasArgNoClaim(OPT_exported_symbol, OPT_exported_symbols_list);
handleSymbolPatterns(args, config->exportedSymbols, OPT_exported_symbol,
OPT_exported_symbols_list);
handleSymbolPatterns(args, config->unexportedSymbols, OPT_unexported_symbol,
OPT_unexported_symbols_list);
- if (!config->exportedSymbols.empty() && !config->unexportedSymbols.empty()) {
- error("cannot use both -exported_symbol* and -unexported_symbol* options\n"
- ">>> ignoring unexports");
- config->unexportedSymbols.clear();
+ if (config->hasExplicitExports && !config->unexportedSymbols.empty())
+ error("cannot use both -exported_symbol* and -unexported_symbol* options");
+
+ if (args.hasArg(OPT_no_exported_symbols) && !config->exportedSymbols.empty())
+ error("cannot use both -exported_symbol* and -no_exported_symbols options");
+
+ // Imitating LD64's:
+ // -non_global_symbols_no_strip_list and -non_global_symbols_strip_list can't
+ // both be present.
+ // But -x can be used with either of these two, in which case, the last arg
+ // takes effect.
+ // (TODO: This is kind of confusing - considering disallowing using them
+ // together for a more straightforward behaviour)
+ {
+ bool includeLocal = false;
+ bool excludeLocal = false;
+ for (const Arg *arg :
+ args.filtered(OPT_x, OPT_non_global_symbols_no_strip_list,
+ OPT_non_global_symbols_strip_list)) {
+ switch (arg->getOption().getID()) {
+ case OPT_x:
+ config->localSymbolsPresence = SymtabPresence::None;
+ break;
+ case OPT_non_global_symbols_no_strip_list:
+ if (excludeLocal) {
+ error("cannot use both -non_global_symbols_no_strip_list and "
+ "-non_global_symbols_strip_list");
+ } else {
+ includeLocal = true;
+ config->localSymbolsPresence = SymtabPresence::SelectivelyIncluded;
+ parseSymbolPatternsFile(arg, config->localSymbolPatterns);
+ }
+ break;
+ case OPT_non_global_symbols_strip_list:
+ if (includeLocal) {
+ error("cannot use both -non_global_symbols_no_strip_list and "
+ "-non_global_symbols_strip_list");
+ } else {
+ excludeLocal = true;
+ config->localSymbolsPresence = SymtabPresence::SelectivelyExcluded;
+ parseSymbolPatternsFile(arg, config->localSymbolPatterns);
+ }
+ break;
+ default:
+ llvm_unreachable("unexpected option");
+ }
+ }
}
// Explicitly-exported literal symbols must be defined, but might
- // languish in an archive if unreferenced elsewhere. Light a fire
- // under those lazy symbols!
+ // languish in an archive if unreferenced elsewhere or if they are in the
+ // non-global strip list. Light a fire under those lazy symbols!
for (const CachedHashStringRef &cachedName : config->exportedSymbols.literals)
symtab->addUndefined(cachedName.val(), /*file=*/nullptr,
/*isWeakRef=*/false);
+ for (const Arg *arg : args.filtered(OPT_why_live))
+ config->whyLive.insert(arg->getValue());
+ if (!config->whyLive.empty() && !config->deadStrip)
+ warn("-why_live has no effect without -dead_strip, ignoring");
+
config->saveTemps = args.hasArg(OPT_save_temps);
config->adhocCodesign = args.hasFlag(
OPT_adhoc_codesign, OPT_no_adhoc_codesign,
- (config->arch() == AK_arm64 || config->arch() == AK_arm64e) &&
- config->platform() == PlatformKind::macOS);
+ shouldAdhocSignByDefault(config->arch(), config->platform()));
if (args.hasArg(OPT_v)) {
- message(getLLDVersion());
+ message(getLLDVersion(), lld::errs());
message(StringRef("Library search paths:") +
- (config->librarySearchPaths.empty()
- ? ""
- : "\n\t" + join(config->librarySearchPaths, "\n\t")));
+ (config->librarySearchPaths.empty()
+ ? ""
+ : "\n\t" + join(config->librarySearchPaths, "\n\t")),
+ lld::errs());
message(StringRef("Framework search paths:") +
- (config->frameworkSearchPaths.empty()
- ? ""
- : "\n\t" + join(config->frameworkSearchPaths, "\n\t")));
+ (config->frameworkSearchPaths.empty()
+ ? ""
+ : "\n\t" + join(config->frameworkSearchPaths, "\n\t")),
+ lld::errs());
}
config->progName = argsArr[0];
- config->timeTraceEnabled = args.hasArg(
- OPT_time_trace, OPT_time_trace_granularity_eq, OPT_time_trace_file_eq);
+ config->timeTraceEnabled = args.hasArg(OPT_time_trace_eq);
config->timeTraceGranularity =
args::getInteger(args, OPT_time_trace_granularity_eq, 500);
initLLVM(); // must be run before any call to addFile()
createFiles(args);
- config->isPic = config->outputType == MH_DYLIB ||
- config->outputType == MH_BUNDLE ||
- (config->outputType == MH_EXECUTE &&
- args.hasFlag(OPT_pie, OPT_no_pie, true));
-
// Now that all dylibs have been loaded, search for those that should be
// re-exported.
{
reexportHandler(arg, extensions);
}
+ cl::ResetAllOptionOccurrences();
+
// Parse LTO options.
if (const Arg *arg = args.getLastArg(OPT_mcpu))
- parseClangOption(saver.save("-mcpu=" + StringRef(arg->getValue())),
+ parseClangOption(saver().save("-mcpu=" + StringRef(arg->getValue())),
arg->getSpelling());
- for (const Arg *arg : args.filtered(OPT_mllvm))
+ for (const Arg *arg : args.filtered(OPT_mllvm)) {
parseClangOption(arg->getValue(), arg->getSpelling());
+ config->mllvmOpts.emplace_back(arg->getValue());
+ }
- compileBitcodeFiles();
+ createSyntheticSections();
+ createSyntheticSymbols();
+ addSynthenticMethnames();
+
+ createAliases();
+ // If we are in "explicit exports" mode, hide everything that isn't
+ // explicitly exported. Do this before running LTO so that LTO can better
+ // optimize.
+ handleExplicitExports();
+
+ bool didCompileBitcodeFiles = compileBitcodeFiles();
+
+ // If --thinlto-index-only is given, we should create only "index
+ // files" and not object files. Index file creation is already done
+ // in compileBitcodeFiles, so we are done if that's the case.
+ if (config->thinLTOIndexOnly)
+ return errorCount() == 0;
+
+ // LTO may emit a non-hidden (extern) object file symbol even if the
+ // corresponding bitcode symbol is hidden. In particular, this happens for
+ // cross-module references to hidden symbols under ThinLTO. Thus, if we
+ // compiled any bitcode files, we must redo the symbol hiding.
+ if (didCompileBitcodeFiles)
+ handleExplicitExports();
replaceCommonSymbols();
StringRef orderFile = args.getLastArgValue(OPT_order_file);
if (!orderFile.empty())
- parseOrderFile(orderFile);
+ priorityBuilder.parseOrderFile(orderFile);
referenceStubBinder();
// FIXME: should terminate the link early based on errors encountered so
// far?
- createSyntheticSections();
- createSyntheticSymbols();
-
- if (!config->exportedSymbols.empty()) {
- for (Symbol *sym : symtab->getSymbols()) {
- if (auto *defined = dyn_cast<Defined>(sym)) {
- StringRef symbolName = defined->getName();
- if (config->exportedSymbols.match(symbolName)) {
- if (defined->privateExtern) {
- error("cannot export hidden symbol " + symbolName +
- "\n>>> defined in " + toString(defined->getFile()));
- }
- } else {
- defined->privateExtern = true;
- }
- }
- }
- } else if (!config->unexportedSymbols.empty()) {
- for (Symbol *sym : symtab->getSymbols())
- if (auto *defined = dyn_cast<Defined>(sym))
- if (config->unexportedSymbols.match(defined->getName()))
- defined->privateExtern = true;
- }
-
for (const Arg *arg : args.filtered(OPT_sectcreate)) {
StringRef segName = arg->getValue(0);
StringRef sectName = arg->getValue(1);
StringRef fileName = arg->getValue(2);
- Optional<MemoryBufferRef> buffer = readFile(fileName);
+ std::optional<MemoryBufferRef> buffer = readFile(fileName);
if (buffer)
inputFiles.insert(make<OpaqueFile>(*buffer, segName, sectName));
}
+ for (const Arg *arg : args.filtered(OPT_add_empty_section)) {
+ StringRef segName = arg->getValue(0);
+ StringRef sectName = arg->getValue(1);
+ inputFiles.insert(make<OpaqueFile>(MemoryBufferRef(), segName, sectName));
+ }
+
gatherInputSections();
+ if (config->callGraphProfileSort)
+ priorityBuilder.extractCallGraphProfile();
if (config->deadStrip)
markLive();
// ICF assumes that all literals have been folded already, so we must run
// foldIdenticalLiterals before foldIdenticalSections.
foldIdenticalLiterals();
- if (config->icfLevel != ICFLevel::none)
- foldIdenticalSections();
+ if (config->icfLevel != ICFLevel::none) {
+ if (config->icfLevel == ICFLevel::safe)
+ markAddrSigSymbols();
+ foldIdenticalSections(/*onlyCfStrings=*/false);
+ } else if (config->dedupStrings) {
+ foldIdenticalSections(/*onlyCfStrings=*/true);
+ }
// Write to an output file.
if (target->wordSize == 8)
}
if (config->timeTraceEnabled) {
- if (auto E = timeTraceProfilerWrite(
- args.getLastArgValue(OPT_time_trace_file_eq).str(),
- config->outputFile)) {
- handleAllErrors(std::move(E),
- [&](const StringError &SE) { error(SE.getMessage()); });
- }
+ checkError(timeTraceProfilerWrite(
+ args.getLastArgValue(OPT_time_trace_eq).str(), config->outputFile));
timeTraceProfilerCleanup();
}
- if (canExitEarly)
- exitLld(errorCount() ? 1 : 0);
+ if (errorCount() != 0 || config->strictAutoLink)
+ for (const auto &warning : missingAutolinkWarnings)
+ warn(warning);
- return !errorCount();
+ return errorCount() == 0;
}
#define LLD_MACHO_DRIVER_H
#include "lld/Common/LLVM.h"
-#include "llvm/ADT/Optional.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Support/MemoryBuffer.h"
+#include <optional>
#include <set>
#include <type_traits>
-namespace llvm {
-namespace MachO {
-class InterfaceFile;
-enum class PlatformKind : unsigned;
-} // namespace MachO
-} // namespace llvm
-
-namespace lld {
-namespace macho {
+namespace lld::macho {
class DylibFile;
class InputFile;
-class MachOOptTable : public llvm::opt::OptTable {
+class MachOOptTable : public llvm::opt::GenericOptTable {
public:
MachOOptTable();
llvm::opt::InputArgList parse(ArrayRef<const char *> argv);
std::string createResponseFile(const llvm::opt::InputArgList &args);
// Check for both libfoo.dylib and libfoo.tbd (in that order).
-llvm::Optional<std::string> resolveDylibPath(llvm::StringRef path);
+std::optional<StringRef> resolveDylibPath(llvm::StringRef path);
DylibFile *loadDylib(llvm::MemoryBufferRef mbref, DylibFile *umbrella = nullptr,
- bool isBundleLoader = false);
+ bool isBundleLoader = false,
+ bool explicitlyLinked = false);
+void resetLoadedDylibs();
// Search for all possible combinations of `{root}/{name}.{extension}`.
// If \p extensions are not specified, then just search for `{root}/{name}`.
-llvm::Optional<llvm::StringRef>
+std::optional<llvm::StringRef>
findPathCombination(const llvm::Twine &name,
const std::vector<llvm::StringRef> &roots,
ArrayRef<llvm::StringRef> extensions = {""});
// rerooted.
llvm::StringRef rerootPath(llvm::StringRef path);
-llvm::Optional<InputFile *> loadArchiveMember(MemoryBufferRef, uint32_t modTime,
- StringRef archiveName,
- bool objCOnly,
- uint64_t offsetInArchive);
-
uint32_t getModTime(llvm::StringRef path);
void printArchiveMemberLoad(StringRef reason, const InputFile *);
// Map simulator platforms to their underlying device platform.
-llvm::MachO::PlatformKind removeSimulator(llvm::MachO::PlatformKind platform);
+llvm::MachO::PlatformType removeSimulator(llvm::MachO::PlatformType platform);
// Helper class to export dependency info.
class DependencyTracker {
notFounds.insert(path.str());
}
- // Writes the dependencies to specified path.
- // The content is sorted by its Op Code, then within each section,
- // alphabetical order.
+ // Writes the dependencies to specified path. The content is first sorted by
+ // OpCode and then by the filename (in alphabetical order).
void write(llvm::StringRef version,
const llvm::SetVector<InputFile *> &inputs,
llvm::StringRef output);
std::set<std::string> notFounds;
};
-extern DependencyTracker *depTracker;
+extern std::unique_ptr<DependencyTracker> depTracker;
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "Target.h"
#include "lld/Common/Args.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Reproduce.h"
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseMap.h"
-#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
using namespace lld::macho;
// Create prefix string literals used in Options.td
-#define PREFIX(NAME, VALUE) const char *NAME[] = VALUE;
+#define PREFIX(NAME, VALUE) \
+ static constexpr StringLiteral NAME##_init[] = VALUE; \
+ static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \
+ std::size(NAME##_init) - 1);
#include "Options.inc"
#undef PREFIX
// Create table mapping all options defined in Options.td
-static const OptTable::Info optInfo[] = {
+static constexpr OptTable::Info optInfo[] = {
#define OPTION(X1, X2, ID, KIND, GROUP, ALIAS, X7, X8, X9, X10, X11, X12) \
{X1, X2, X10, X11, OPT_##ID, Option::KIND##Class, \
X9, X8, OPT_##GROUP, OPT_##ALIAS, X7, X12},
#undef OPTION
};
-MachOOptTable::MachOOptTable() : OptTable(optInfo) {}
+MachOOptTable::MachOOptTable() : GenericOptTable(optInfo) {}
// Set color diagnostics according to --color-diagnostics={auto,always,never}
// or --no-color-diagnostics flags.
// Expand response files (arguments in the form of @<filename>)
// and then parse the argument again.
- cl::ExpandResponseFiles(saver, cl::TokenizeGNUCommandLine, vec);
+ cl::ExpandResponseFiles(saver(), cl::TokenizeGNUCommandLine, vec);
InputArgList args = ParseArgs(vec, missingIndex, missingCount);
// Handle -fatal_warnings early since it converts missing argument warnings
// to errors.
errorHandler().fatalWarnings = args.hasArg(OPT_fatal_warnings);
+ errorHandler().suppressWarnings = args.hasArg(OPT_w);
if (missingCount)
error(Twine(args.getArgString(missingIndex)) + ": missing argument");
os << "-o " << quote(path::filename(arg->getValue())) << "\n";
break;
case OPT_filelist:
- if (Optional<MemoryBufferRef> buffer = readFile(arg->getValue()))
+ if (std::optional<MemoryBufferRef> buffer = readFile(arg->getValue()))
for (StringRef path : args::getLines(*buffer))
os << quote(rewriteInputPath(path)) << "\n";
break;
case OPT_force_load:
case OPT_weak_library:
+ case OPT_load_hidden:
os << arg->getSpelling() << " "
<< quote(rewriteInputPath(arg->getValue())) << "\n";
break;
case OPT_bundle_loader:
case OPT_exported_symbols_list:
case OPT_order_file:
- case OPT_rpath:
case OPT_syslibroot:
case OPT_unexported_symbols_list:
os << arg->getSpelling() << " " << quote(rewritePath(arg->getValue()))
depTracker->logFileNotFound(path);
}
-Optional<std::string> macho::resolveDylibPath(StringRef dylibPath) {
+std::optional<StringRef> macho::resolveDylibPath(StringRef dylibPath) {
// TODO: if a tbd and dylib are both present, we should check to make sure
// they are consistent.
- bool dylibExists = fs::exists(dylibPath);
- searchedDylib(dylibPath, dylibExists);
- if (dylibExists)
- return std::string(dylibPath);
-
SmallString<261> tbdPath = dylibPath;
path::replace_extension(tbdPath, ".tbd");
bool tbdExists = fs::exists(tbdPath);
searchedDylib(tbdPath, tbdExists);
if (tbdExists)
- return std::string(tbdPath);
+ return saver().save(tbdPath.str());
+
+ bool dylibExists = fs::exists(dylibPath);
+ searchedDylib(dylibPath, dylibExists);
+ if (dylibExists)
+ return saver().save(dylibPath);
return {};
}
static DenseMap<CachedHashStringRef, DylibFile *> loadedDylibs;
DylibFile *macho::loadDylib(MemoryBufferRef mbref, DylibFile *umbrella,
- bool isBundleLoader) {
+ bool isBundleLoader, bool explicitlyLinked) {
CachedHashStringRef path(mbref.getBufferIdentifier());
DylibFile *&file = loadedDylibs[path];
- if (file)
+ if (file) {
+ if (explicitlyLinked)
+ file->setExplicitlyLinked();
return file;
+ }
DylibFile *newFile;
file_magic magic = identify_magic(mbref.getBuffer());
": " + toString(result.takeError()));
return nullptr;
}
- file = make<DylibFile>(**result, umbrella, isBundleLoader);
+ file =
+ make<DylibFile>(**result, umbrella, isBundleLoader, explicitlyLinked);
// parseReexports() can recursively call loadDylib(). That's fine since
// we wrote the DylibFile we just loaded to the loadDylib cache via the
magic == file_magic::macho_dynamically_linked_shared_lib_stub ||
magic == file_magic::macho_executable ||
magic == file_magic::macho_bundle);
- file = make<DylibFile>(mbref, umbrella, isBundleLoader);
+ file = make<DylibFile>(mbref, umbrella, isBundleLoader, explicitlyLinked);
// parseLoadCommands() can also recursively call loadDylib(). See comment
// in previous block for why this means we must copy `file` here.
return newFile;
}
-Optional<StringRef>
+void macho::resetLoadedDylibs() { loadedDylibs.clear(); }
+
+std::optional<StringRef>
macho::findPathCombination(const Twine &name,
const std::vector<StringRef> &roots,
ArrayRef<StringRef> extensions) {
bool exists = fs::exists(location);
searchedDylib(location, exists);
if (exists)
- return saver.save(location.str());
+ return saver().save(location.str());
}
}
return {};
if (!path::is_absolute(path, path::Style::posix) || path.endswith(".o"))
return path;
- if (Optional<StringRef> rerootedPath =
+ if (std::optional<StringRef> rerootedPath =
findPathCombination(path, config->systemLibraryRoots))
return *rerootedPath;
return path;
}
-Optional<InputFile *> macho::loadArchiveMember(MemoryBufferRef mb,
- uint32_t modTime,
- StringRef archiveName,
- bool objCOnly,
- uint64_t offsetInArchive) {
- if (config->zeroModTime)
- modTime = 0;
-
- switch (identify_magic(mb.getBuffer())) {
- case file_magic::macho_object:
- if (!objCOnly || hasObjCSection(mb))
- return make<ObjFile>(mb, modTime, archiveName);
- return None;
- case file_magic::bitcode:
- if (!objCOnly || check(isBitcodeContainingObjCCategory(mb)))
- return make<BitcodeFile>(mb, archiveName, offsetInArchive);
- return None;
- default:
- error(archiveName + ": archive member " + mb.getBufferIdentifier() +
- " has unhandled file type");
- return None;
- }
-}
-
uint32_t macho::getModTime(StringRef path) {
if (config->zeroModTime)
return 0;
std::unique_ptr<DwarfObject> DwarfObject::create(ObjFile *obj) {
auto dObj = std::make_unique<DwarfObject>();
bool hasDwarfInfo = false;
- // LLD only needs to extract the source file path from the debug info, so we
- // initialize DwarfObject with just the sections necessary to get that path.
- // The debugger will locate the debug info via the object file paths that we
- // emit in our STABS symbols, so we don't need to process & emit them
- // ourselves.
+ // LLD only needs to extract the source file path and line numbers from the
+ // debug info, so we initialize DwarfObject with just the sections necessary
+ // to get that path. The debugger will locate the debug info via the object
+ // file paths that we emit in our STABS symbols, so we don't need to process &
+ // emit them ourselves.
for (const InputSection *isec : obj->debugSections) {
if (StringRef *s =
StringSwitch<StringRef *>(isec->getName())
.Case(section_names::debugInfo, &dObj->infoSection.Data)
+ .Case(section_names::debugLine, &dObj->lineSection.Data)
+ .Case(section_names::debugStrOffs, &dObj->strOffsSection.Data)
.Case(section_names::debugAbbrev, &dObj->abbrevSection)
.Case(section_names::debugStr, &dObj->strSection)
.Default(nullptr)) {
#include "llvm/ADT/StringRef.h"
#include "llvm/DebugInfo/DWARF/DWARFObject.h"
-namespace lld {
-namespace macho {
+namespace lld::macho {
class ObjFile;
public:
bool isLittleEndian() const override { return true; }
- llvm::Optional<llvm::RelocAddrEntry> find(const llvm::DWARFSection &sec,
- uint64_t pos) const override {
+ std::optional<llvm::RelocAddrEntry> find(const llvm::DWARFSection &sec,
+ uint64_t pos) const override {
// TODO: implement this
- return llvm::None;
+ return std::nullopt;
}
void forEachInfoSections(
llvm::StringRef getAbbrevSection() const override { return abbrevSection; }
llvm::StringRef getStrSection() const override { return strSection; }
+ llvm::DWARFSection const &getLineSection() const override {
+ return lineSection;
+ }
+
+ llvm::DWARFSection const &getStrOffsetsSection() const override {
+ return strOffsSection;
+ }
+
// Returns an instance of DwarfObject if the given object file has the
// relevant DWARF debug sections.
static std::unique_ptr<DwarfObject> create(ObjFile *);
private:
llvm::DWARFSection infoSection;
+ llvm::DWARFSection lineSection;
+ llvm::DWARFSection strOffsSection;
llvm::StringRef abbrevSection;
llvm::StringRef strSection;
};
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
--- /dev/null
+//===- EhFrame.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 "EhFrame.h"
+#include "InputFiles.h"
+
+#include "lld/Common/ErrorHandler.h"
+#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/Support/Endian.h"
+
+using namespace llvm;
+using namespace lld;
+using namespace lld::macho;
+using namespace llvm::support::endian;
+
+uint64_t EhReader::readLength(size_t *off) const {
+ const size_t errOff = *off;
+ if (*off + 4 > data.size())
+ failOn(errOff, "CIE/FDE too small");
+ uint64_t len = read32le(data.data() + *off);
+ *off += 4;
+ if (len == dwarf::DW_LENGTH_DWARF64) {
+ // FIXME: test this DWARF64 code path
+ if (*off + 8 > data.size())
+ failOn(errOff, "CIE/FDE too small");
+ len = read64le(data.data() + *off);
+ *off += 8;
+ }
+ if (*off + len > data.size())
+ failOn(errOff, "CIE/FDE extends past the end of the section");
+ return len;
+}
+
+void EhReader::skipValidLength(size_t *off) const {
+ uint32_t len = read32le(data.data() + *off);
+ *off += 4;
+ if (len == dwarf::DW_LENGTH_DWARF64)
+ *off += 8;
+}
+
+// Read a byte and advance off by one byte.
+uint8_t EhReader::readByte(size_t *off) const {
+ if (*off + 1 > data.size())
+ failOn(*off, "unexpected end of CIE/FDE");
+ return data[(*off)++];
+}
+
+uint32_t EhReader::readU32(size_t *off) const {
+ if (*off + 4 > data.size())
+ failOn(*off, "unexpected end of CIE/FDE");
+ uint32_t v = read32le(data.data() + *off);
+ *off += 4;
+ return v;
+}
+
+uint64_t EhReader::readPointer(size_t *off, uint8_t size) const {
+ if (*off + size > data.size())
+ failOn(*off, "unexpected end of CIE/FDE");
+ uint64_t v;
+ if (size == 8)
+ v = read64le(data.data() + *off);
+ else {
+ assert(size == 4);
+ v = read32le(data.data() + *off);
+ }
+ *off += size;
+ return v;
+}
+
+// Read a null-terminated string.
+StringRef EhReader::readString(size_t *off) const {
+ if (*off > data.size())
+ failOn(*off, "corrupted CIE (failed to read string)");
+ const size_t maxlen = data.size() - *off;
+ auto *c = reinterpret_cast<const char *>(data.data() + *off);
+ size_t len = strnlen(c, maxlen);
+ if (len == maxlen) // we failed to find the null terminator
+ failOn(*off, "corrupted CIE (failed to read string)");
+ *off += len + 1; // skip the null byte too
+ return StringRef(c, len);
+}
+
+void EhReader::skipLeb128(size_t *off) const {
+ const size_t errOff = *off;
+ while (*off < data.size()) {
+ uint8_t val = data[(*off)++];
+ if ((val & 0x80) == 0)
+ return;
+ }
+ failOn(errOff, "corrupted CIE (failed to read LEB128)");
+}
+
+void EhReader::failOn(size_t errOff, const Twine &msg) const {
+ fatal(toString(file) + ":(__eh_frame+0x" +
+ Twine::utohexstr(dataOff + errOff) + "): " + msg);
+}
+
+/*
+ * Create a pair of relocs to write the value of:
+ * `b - (offset + a)` if Invert == false
+ * `(a + offset) - b` if Invert == true
+ */
+template <bool Invert = false>
+static void createSubtraction(PointerUnion<Symbol *, InputSection *> a,
+ PointerUnion<Symbol *, InputSection *> b,
+ uint64_t off, uint8_t length,
+ SmallVectorImpl<Reloc> *newRelocs) {
+ auto subtrahend = a;
+ auto minuend = b;
+ if (Invert)
+ std::swap(subtrahend, minuend);
+ assert(subtrahend.is<Symbol *>());
+ Reloc subtrahendReloc(target->subtractorRelocType, /*pcrel=*/false, length,
+ off, /*addend=*/0, subtrahend);
+ Reloc minuendReloc(target->unsignedRelocType, /*pcrel=*/false, length, off,
+ (Invert ? 1 : -1) * off, minuend);
+ newRelocs->push_back(subtrahendReloc);
+ newRelocs->push_back(minuendReloc);
+}
+
+void EhRelocator::makePcRel(uint64_t off,
+ PointerUnion<Symbol *, InputSection *> target,
+ uint8_t length) {
+ createSubtraction(isec->symbols[0], target, off, length, &newRelocs);
+}
+
+void EhRelocator::makeNegativePcRel(
+ uint64_t off, PointerUnion<Symbol *, InputSection *> target,
+ uint8_t length) {
+ createSubtraction</*Invert=*/true>(isec, target, off, length, &newRelocs);
+}
+
+void EhRelocator::commit() {
+ isec->relocs.insert(isec->relocs.end(), newRelocs.begin(), newRelocs.end());
+}
--- /dev/null
+//===- EhFrame.h ------------------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLD_MACHO_EH_FRAME_H
+#define LLD_MACHO_EH_FRAME_H
+
+#include "InputSection.h"
+#include "Relocations.h"
+
+#include "lld/Common/LLVM.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/PointerUnion.h"
+#include "llvm/ADT/SmallVector.h"
+
+/*
+ * NOTE: The main bulk of the EH frame parsing logic is in InputFiles.cpp as it
+ * is closely coupled with other file parsing logic; EhFrame.h just contains a
+ * few helpers.
+ */
+
+/*
+ * === The EH frame format ===
+ *
+ * EH frames can either be Common Information Entries (CIEs) or Frame
+ * Description Entries (FDEs). CIEs contain information that is common amongst
+ * several FDEs. Each FDE contains a pointer to its CIE. Thus all the EH frame
+ * entries together form a forest of two-level trees, with CIEs as the roots
+ * and FDEs as the leaves. Note that a CIE must precede the FDEs which point
+ * to it.
+ *
+ * A CIE comprises the following fields in order:
+ * 1. Length of the entry (4 or 12 bytes)
+ * 2. CIE offset (4 bytes; always 0 for CIEs)
+ * 3. CIE version (byte)
+ * 4. Null-terminated augmentation string
+ * 5-8. LEB128 values that we don't care about
+ * 9. Augmentation data, to be interpreted using the aug string
+ * 10. DWARF instructions (ignored by LLD)
+ *
+ * An FDE comprises of the following:
+ * 1. Length of the entry (4 or 12 bytes)
+ * 2. CIE offset (4 bytes pcrel offset that points backwards to this FDE's CIE)
+ * 3. Function address (pointer-sized pcrel offset)
+ * 4. (std::optional) Augmentation data length
+ * 5. (std::optional) LSDA address (pointer-sized pcrel offset)
+ * 6. DWARF instructions (ignored by LLD)
+ */
+namespace lld::macho {
+
+class EhReader {
+public:
+ EhReader(const ObjFile *file, ArrayRef<uint8_t> data, size_t dataOff)
+ : file(file), data(data), dataOff(dataOff) {}
+ size_t size() const { return data.size(); }
+ // Read and validate the length field.
+ uint64_t readLength(size_t *off) const;
+ // Skip the length field without doing validation.
+ void skipValidLength(size_t *off) const;
+ uint8_t readByte(size_t *off) const;
+ uint32_t readU32(size_t *off) const;
+ uint64_t readPointer(size_t *off, uint8_t size) const;
+ StringRef readString(size_t *off) const;
+ void skipLeb128(size_t *off) const;
+ void failOn(size_t errOff, const Twine &msg) const;
+
+private:
+ const ObjFile *file;
+ ArrayRef<uint8_t> data;
+ // The offset of the data array within its section. Used only for error
+ // reporting.
+ const size_t dataOff;
+};
+
+// The EH frame format, when emitted by llvm-mc, consists of a number of
+// "abs-ified" relocations, i.e. relocations that are implicitly encoded as
+// pcrel offsets in the section data. The offsets refer to the locations of
+// symbols in the input object file. When we ingest these EH frames, we convert
+// these implicit relocations into explicit Relocs.
+//
+// These pcrel relocations are semantically similar to X86_64_RELOC_SIGNED_4.
+// However, we need this operation to be cross-platform, and ARM does not have a
+// similar relocation that is applicable. We therefore use the more verbose (but
+// more generic) subtractor relocation to encode these pcrel values. ld64
+// appears to do something similar -- its `-r` output contains these explicit
+// subtractor relocations.
+class EhRelocator {
+public:
+ EhRelocator(InputSection *isec) : isec(isec) {}
+
+ // For the next two methods, let `PC` denote `isec address + off`.
+ // Create relocs writing the value of target - PC to PC.
+ void makePcRel(uint64_t off,
+ llvm::PointerUnion<Symbol *, InputSection *> target,
+ uint8_t length);
+ // Create relocs writing the value of PC - target to PC.
+ void makeNegativePcRel(uint64_t off,
+ llvm::PointerUnion<Symbol *, InputSection *> target,
+ uint8_t length);
+ // Insert the new relocations into isec->relocs.
+ void commit();
+
+private:
+ InputSection *isec;
+ // Insert new relocs here so that we don't invalidate iterators into the
+ // existing relocs vector.
+ SmallVector<Reloc, 6> newRelocs;
+};
+
+} // namespace lld::macho
+
+#endif
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
-#include "llvm/ADT/Optional.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/LEB128.h"
+#include <optional>
using namespace llvm;
using namespace lld;
struct macho::TrieNode {
std::vector<Edge> edges;
- Optional<ExportInfo> info;
+ std::optional<ExportInfo> info;
// Estimated offset from the start of the serialized trie to the current node.
// This will converge to the true offset when updateOffset() is run to a
// fixpoint.
}
}
+TrieBuilder::~TrieBuilder() {
+ for (TrieNode *node : nodes)
+ delete node;
+}
+
TrieNode *TrieBuilder::makeNode() {
- auto *node = make<TrieNode>();
+ auto *node = new TrieNode();
nodes.emplace_back(node);
return node;
}
#include <vector>
-namespace lld {
-namespace macho {
+namespace lld::macho {
struct TrieNode;
class Symbol;
class TrieBuilder {
public:
+ ~TrieBuilder();
void setImageBase(uint64_t addr) { imageBase = addr; }
void addSymbol(const Symbol &sym) { exported.push_back(&sym); }
// Returns the size in bytes of the serialized trie.
void parseTrie(const uint8_t *buf, size_t size, const TrieEntryCallback &);
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "ICF.h"
#include "ConcatOutputSection.h"
+#include "Config.h"
#include "InputSection.h"
+#include "SymbolTable.h"
#include "Symbols.h"
#include "UnwindInfoSection.h"
+#include "lld/Common/CommonLinkerContext.h"
+#include "llvm/Support/LEB128.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/TimeProfiler.h"
+#include "llvm/Support/xxhash.h"
#include <atomic>
using namespace lld;
using namespace lld::macho;
+static constexpr bool verboseDiagnostics = false;
+
class ICF {
public:
ICF(std::vector<ConcatInputSection *> &inputs);
-
void run();
- void segregate(size_t begin, size_t end,
- std::function<bool(const ConcatInputSection *,
- const ConcatInputSection *)>
- equals);
+
+ using EqualsFn = bool (ICF::*)(const ConcatInputSection *,
+ const ConcatInputSection *);
+ void segregate(size_t begin, size_t end, EqualsFn);
size_t findBoundary(size_t begin, size_t end);
void forEachClassRange(size_t begin, size_t end,
- std::function<void(size_t, size_t)> func);
- void forEachClass(std::function<void(size_t, size_t)> func);
+ llvm::function_ref<void(size_t, size_t)> func);
+ void forEachClass(llvm::function_ref<void(size_t, size_t)> func);
+
+ bool equalsConstant(const ConcatInputSection *ia,
+ const ConcatInputSection *ib);
+ bool equalsVariable(const ConcatInputSection *ia,
+ const ConcatInputSection *ib);
// ICF needs a copy of the inputs vector because its equivalence-class
// segregation algorithm destroys the proper sequence.
std::vector<ConcatInputSection *> icfInputs;
+
+ unsigned icfPass = 0;
+ std::atomic<bool> icfRepeat{false};
+ std::atomic<uint64_t> equalsConstantCount{0};
+ std::atomic<uint64_t> equalsVariableCount{0};
};
ICF::ICF(std::vector<ConcatInputSection *> &inputs) {
// FIXME(gkm): implement keep-unique attributes
// FIXME(gkm): implement address-significance tables for MachO object files
-static unsigned icfPass = 0;
-static std::atomic<bool> icfRepeat{false};
-
// Compare "non-moving" parts of two ConcatInputSections, namely everything
// except references to other ConcatInputSections.
-static bool equalsConstant(const ConcatInputSection *ia,
- const ConcatInputSection *ib) {
+bool ICF::equalsConstant(const ConcatInputSection *ia,
+ const ConcatInputSection *ib) {
+ if (verboseDiagnostics)
+ ++equalsConstantCount;
// We can only fold within the same OutputSection.
if (ia->parent != ib->parent)
return false;
return false;
if (ra.offset != rb.offset)
return false;
- if (ra.addend != rb.addend)
- return false;
if (ra.referent.is<Symbol *>() != rb.referent.is<Symbol *>())
return false;
InputSection *isecA, *isecB;
+
+ uint64_t valueA = 0;
+ uint64_t valueB = 0;
if (ra.referent.is<Symbol *>()) {
const auto *sa = ra.referent.get<Symbol *>();
const auto *sb = rb.referent.get<Symbol *>();
if (sa->kind() != sb->kind())
return false;
- if (isa<Defined>(sa)) {
- const auto *da = cast<Defined>(sa);
- const auto *db = cast<Defined>(sb);
- if (da->isec && db->isec) {
- isecA = da->isec;
- isecB = db->isec;
- } else {
- assert(da->isAbsolute() && db->isAbsolute());
- return da->value == db->value;
- }
- } else {
- assert(isa<DylibSymbol>(sa));
- return sa == sb;
+ // ICF runs before Undefineds are treated (and potentially converted into
+ // DylibSymbols).
+ if (isa<DylibSymbol>(sa) || isa<Undefined>(sa))
+ return sa == sb && ra.addend == rb.addend;
+ assert(isa<Defined>(sa));
+ const auto *da = cast<Defined>(sa);
+ const auto *db = cast<Defined>(sb);
+ if (!da->isec || !db->isec) {
+ assert(da->isAbsolute() && db->isAbsolute());
+ return da->value + ra.addend == db->value + rb.addend;
}
+ isecA = da->isec;
+ valueA = da->value;
+ isecB = db->isec;
+ valueB = db->value;
} else {
isecA = ra.referent.get<InputSection *>();
isecB = rb.referent.get<InputSection *>();
assert(isecA->kind() == isecB->kind());
// We will compare ConcatInputSection contents in equalsVariable.
if (isa<ConcatInputSection>(isecA))
- return true;
+ return ra.addend == rb.addend;
// Else we have two literal sections. References to them are equal iff their
// offsets in the output section are equal.
- return isecA->getOffset(ra.addend) == isecB->getOffset(rb.addend);
+ if (ra.referent.is<Symbol *>())
+ // For symbol relocs, we compare the contents at the symbol address. We
+ // don't do `getOffset(value + addend)` because value + addend may not be
+ // a valid offset in the literal section.
+ return isecA->getOffset(valueA) == isecB->getOffset(valueB) &&
+ ra.addend == rb.addend;
+ else {
+ assert(valueA == 0 && valueB == 0);
+ // For section relocs, we compare the content at the section offset.
+ return isecA->getOffset(ra.addend) == isecB->getOffset(rb.addend);
+ }
};
return std::equal(ia->relocs.begin(), ia->relocs.end(), ib->relocs.begin(),
f);
// Compare the "moving" parts of two ConcatInputSections -- i.e. everything not
// handled by equalsConstant().
-static bool equalsVariable(const ConcatInputSection *ia,
- const ConcatInputSection *ib) {
+bool ICF::equalsVariable(const ConcatInputSection *ia,
+ const ConcatInputSection *ib) {
+ if (verboseDiagnostics)
+ ++equalsVariableCount;
assert(ia->relocs.size() == ib->relocs.size());
- auto f = [](const Reloc &ra, const Reloc &rb) {
+ auto f = [this](const Reloc &ra, const Reloc &rb) {
// We already filtered out mismatching values/addends in equalsConstant.
if (ra.referent == rb.referent)
return true;
}
return isecA->icfEqClass[icfPass % 2] == isecB->icfEqClass[icfPass % 2];
};
- return std::equal(ia->relocs.begin(), ia->relocs.end(), ib->relocs.begin(),
- f);
+ if (!std::equal(ia->relocs.begin(), ia->relocs.end(), ib->relocs.begin(), f))
+ return false;
+
+ // If there are symbols with associated unwind info, check that the unwind
+ // info matches. For simplicity, we only handle the case where there are only
+ // symbols at offset zero within the section (which is typically the case with
+ // .subsections_via_symbols.)
+ auto hasUnwind = [](Defined *d) { return d->unwindEntry != nullptr; };
+ auto itA = std::find_if(ia->symbols.begin(), ia->symbols.end(), hasUnwind);
+ auto itB = std::find_if(ib->symbols.begin(), ib->symbols.end(), hasUnwind);
+ if (itA == ia->symbols.end())
+ return itB == ib->symbols.end();
+ if (itB == ib->symbols.end())
+ return false;
+ const Defined *da = *itA;
+ const Defined *db = *itB;
+ if (da->unwindEntry->icfEqClass[icfPass % 2] !=
+ db->unwindEntry->icfEqClass[icfPass % 2] ||
+ da->value != 0 || db->value != 0)
+ return false;
+ auto isZero = [](Defined *d) { return d->value == 0; };
+ return std::find_if_not(std::next(itA), ia->symbols.end(), isZero) ==
+ ia->symbols.end() &&
+ std::find_if_not(std::next(itB), ib->symbols.end(), isZero) ==
+ ib->symbols.end();
}
// Find the first InputSection after BEGIN whose equivalence class differs
// Invoke FUNC on subranges with matching equivalence class
void ICF::forEachClassRange(size_t begin, size_t end,
- std::function<void(size_t, size_t)> func) {
+ llvm::function_ref<void(size_t, size_t)> func) {
while (begin < end) {
size_t mid = findBoundary(begin, end);
func(begin, mid);
// Split icfInputs into shards, then parallelize invocation of FUNC on subranges
// with matching equivalence class
-void ICF::forEachClass(std::function<void(size_t, size_t)> func) {
+void ICF::forEachClass(llvm::function_ref<void(size_t, size_t)> func) {
// Only use threads when the benefits outweigh the overhead.
const size_t threadingThreshold = 1024;
if (icfInputs.size() < threadingThreshold) {
size_t boundaries[shards + 1];
boundaries[0] = 0;
boundaries[shards] = icfInputs.size();
- parallelForEachN(1, shards, [&](size_t i) {
+ parallelFor(1, shards, [&](size_t i) {
boundaries[i] = findBoundary((i - 1) * step, icfInputs.size());
});
- parallelForEachN(1, shards + 1, [&](size_t i) {
+ parallelFor(1, shards + 1, [&](size_t i) {
if (boundaries[i - 1] < boundaries[i]) {
forEachClassRange(boundaries[i - 1], boundaries[i], func);
}
// Into each origin-section hash, combine all reloc referent section hashes.
for (icfPass = 0; icfPass < 2; ++icfPass) {
parallelForEach(icfInputs, [&](ConcatInputSection *isec) {
- uint64_t hash = isec->icfEqClass[icfPass % 2];
+ uint32_t hash = isec->icfEqClass[icfPass % 2];
for (const Reloc &r : isec->relocs) {
if (auto *sym = r.referent.dyn_cast<Symbol *>()) {
- if (auto *dylibSym = dyn_cast<DylibSymbol>(sym))
- hash += dylibSym->stubsHelperIndex;
- else if (auto *defined = dyn_cast<Defined>(sym)) {
+ if (auto *defined = dyn_cast<Defined>(sym)) {
if (defined->isec) {
- if (auto isec = dyn_cast<ConcatInputSection>(defined->isec))
- hash += defined->value + isec->icfEqClass[icfPass % 2];
+ if (auto referentIsec =
+ dyn_cast<ConcatInputSection>(defined->isec))
+ hash += defined->value + referentIsec->icfEqClass[icfPass % 2];
else
hash += defined->isec->kind() +
defined->isec->getOffset(defined->value);
} else {
hash += defined->value;
}
- } else
- llvm_unreachable("foldIdenticalSections symbol kind");
+ } else {
+ // ICF runs before Undefined diags
+ assert(isa<Undefined>(sym) || isa<DylibSymbol>(sym));
+ }
}
}
// Set MSB to 1 to avoid collisions with non-hashed classes.
- isec->icfEqClass[(icfPass + 1) % 2] = hash | (1ull << 63);
+ isec->icfEqClass[(icfPass + 1) % 2] = hash | (1ull << 31);
});
}
icfInputs, [](const ConcatInputSection *a, const ConcatInputSection *b) {
return a->icfEqClass[0] < b->icfEqClass[0];
});
- forEachClass(
- [&](size_t begin, size_t end) { segregate(begin, end, equalsConstant); });
+ forEachClass([&](size_t begin, size_t end) {
+ segregate(begin, end, &ICF::equalsConstant);
+ });
// Split equivalence groups by comparing relocations until convergence
do {
icfRepeat = false;
forEachClass([&](size_t begin, size_t end) {
- segregate(begin, end, equalsVariable);
+ segregate(begin, end, &ICF::equalsVariable);
});
} while (icfRepeat);
log("ICF needed " + Twine(icfPass) + " iterations");
+ if (verboseDiagnostics) {
+ log("equalsConstant() called " + Twine(equalsConstantCount) + " times");
+ log("equalsVariable() called " + Twine(equalsVariableCount) + " times");
+ }
// Fold sections within equivalence classes
forEachClass([&](size_t begin, size_t end) {
}
// Split an equivalence class into smaller classes.
-void ICF::segregate(
- size_t begin, size_t end,
- std::function<bool(const ConcatInputSection *, const ConcatInputSection *)>
- equals) {
+void ICF::segregate(size_t begin, size_t end, EqualsFn equals) {
while (begin < end) {
// Divide [begin, end) into two. Let mid be the start index of the
// second group.
- auto bound = std::stable_partition(icfInputs.begin() + begin + 1,
- icfInputs.begin() + end,
- [&](ConcatInputSection *isec) {
- return equals(icfInputs[begin], isec);
- });
+ auto bound = std::stable_partition(
+ icfInputs.begin() + begin + 1, icfInputs.begin() + end,
+ [&](ConcatInputSection *isec) {
+ return (this->*equals)(icfInputs[begin], isec);
+ });
size_t mid = bound - icfInputs.begin();
// Split [begin, end) into [begin, mid) and [mid, end). We use mid as an
}
}
-template <class Ptr>
-DenseSet<const InputSection *> findFunctionsWithUnwindInfo() {
- DenseSet<const InputSection *> result;
- for (ConcatInputSection *isec : in.unwindInfo->getInputs()) {
- for (size_t i = 0; i < isec->relocs.size(); ++i) {
- Reloc &r = isec->relocs[i];
- assert(target->hasAttr(r.type, RelocAttrBits::UNSIGNED));
- if (r.offset % sizeof(CompactUnwindEntry<Ptr>) !=
- offsetof(CompactUnwindEntry<Ptr>, functionAddress))
- continue;
- result.insert(r.referent.get<InputSection *>());
+void macho::markSymAsAddrSig(Symbol *s) {
+ if (auto *d = dyn_cast_or_null<Defined>(s))
+ if (d->isec)
+ d->isec->keepUnique = true;
+}
+
+void macho::markAddrSigSymbols() {
+ TimeTraceScope timeScope("Mark addrsig symbols");
+ for (InputFile *file : inputFiles) {
+ ObjFile *obj = dyn_cast<ObjFile>(file);
+ if (!obj)
+ continue;
+
+ Section *addrSigSection = obj->addrSigSection;
+ if (!addrSigSection)
+ continue;
+ assert(addrSigSection->subsections.size() == 1);
+
+ const InputSection *isec = addrSigSection->subsections[0].isec;
+
+ for (const Reloc &r : isec->relocs) {
+ if (auto *sym = r.referent.dyn_cast<Symbol *>())
+ markSymAsAddrSig(sym);
+ else
+ error(toString(isec) + ": unexpected section relocation");
}
}
- return result;
}
-void macho::foldIdenticalSections() {
+void macho::foldIdenticalSections(bool onlyCfStrings) {
TimeTraceScope timeScope("Fold Identical Code Sections");
// The ICF equivalence-class segregation algorithm relies on pre-computed
// hashes of InputSection::data for the ConcatOutputSection::inputs and all
// parallelization. Therefore, we hash every InputSection here where we have
// them all accessible as simple vectors.
- // ICF can't fold functions with unwind info
- DenseSet<const InputSection *> functionsWithUnwindInfo =
- target->wordSize == 8 ? findFunctionsWithUnwindInfo<uint64_t>()
- : findFunctionsWithUnwindInfo<uint32_t>();
-
// If an InputSection is ineligible for ICF, we give it a unique ID to force
// it into an unfoldable singleton equivalence class. Begin the unique-ID
// space at inputSections.size(), so that it will never intersect with
// coexist with equivalence-class IDs, this is not necessary, but might help
// someone keep the numbers straight in case we ever need to debug the
// ICF::segregate()
- std::vector<ConcatInputSection *> hashable;
+ std::vector<ConcatInputSection *> foldable;
uint64_t icfUniqueID = inputSections.size();
for (ConcatInputSection *isec : inputSections) {
- // FIXME: consider non-code __text sections as hashable?
- bool isHashable = (isCodeSection(isec) || isCfStringSection(isec)) &&
- !isec->shouldOmitFromOutput() &&
- !functionsWithUnwindInfo.contains(isec) &&
- isec->isHashableForICF();
- if (isHashable)
- hashable.push_back(isec);
- else
+ bool isFoldableWithAddendsRemoved = isCfStringSection(isec) ||
+ isClassRefsSection(isec) ||
+ isSelRefsSection(isec);
+ // NOTE: __objc_selrefs is typically marked as no_dead_strip by MC, but we
+ // can still fold it.
+ bool hasFoldableFlags = (isSelRefsSection(isec) ||
+ sectionType(isec->getFlags()) == MachO::S_REGULAR);
+ // FIXME: consider non-code __text sections as foldable?
+ bool isFoldable = (!onlyCfStrings || isCfStringSection(isec)) &&
+ (isCodeSection(isec) || isFoldableWithAddendsRemoved ||
+ isGccExceptTabSection(isec)) &&
+ !isec->keepUnique && !isec->hasAltEntry &&
+ !isec->shouldOmitFromOutput() && hasFoldableFlags;
+ if (isFoldable) {
+ foldable.push_back(isec);
+ for (Defined *d : isec->symbols)
+ if (d->unwindEntry)
+ foldable.push_back(d->unwindEntry);
+
+ // Some sections have embedded addends that foil ICF's hashing / equality
+ // checks. (We can ignore embedded addends when doing ICF because the same
+ // information gets recorded in our Reloc structs.) We therefore create a
+ // mutable copy of the section data and zero out the embedded addends
+ // before performing any hashing / equality checks.
+ if (isFoldableWithAddendsRemoved) {
+ // We have to do this copying serially as the BumpPtrAllocator is not
+ // thread-safe. FIXME: Make a thread-safe allocator.
+ MutableArrayRef<uint8_t> copy = isec->data.copy(bAlloc());
+ for (const Reloc &r : isec->relocs)
+ target->relocateOne(copy.data() + r.offset, r, /*va=*/0,
+ /*relocVA=*/0);
+ isec->data = copy;
+ }
+ } else if (!isEhFrameSection(isec)) {
+ // EH frames are gathered as foldables from unwindEntry above; give a
+ // unique ID to everything else.
isec->icfEqClass[0] = ++icfUniqueID;
+ }
}
- parallelForEach(hashable,
- [](ConcatInputSection *isec) { isec->hashForICF(); });
+ parallelForEach(foldable, [](ConcatInputSection *isec) {
+ assert(isec->icfEqClass[0] == 0); // don't overwrite a unique ID!
+ // Turn-on the top bit to guarantee that valid hashes have no collisions
+ // with the small-integer unique IDs for ICF-ineligible sections
+ isec->icfEqClass[0] = xxHash64(isec->data) | (1ull << 31);
+ });
// Now that every input section is either hashed or marked as unique, run the
// segregation algorithm to detect foldable subsections.
- ICF(hashable).run();
+ ICF(foldable).run();
}
#ifndef LLD_MACHO_ICF_H
#define LLD_MACHO_ICF_H
+#include "InputFiles.h"
#include "lld/Common/LLVM.h"
#include <vector>
-namespace lld {
-namespace macho {
+namespace lld::macho {
+class Symbol;
-void foldIdenticalSections();
+void markAddrSigSymbols();
+void markSymAsAddrSig(Symbol *s);
+void foldIdenticalSections(bool onlyCfStrings);
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "Config.h"
#include "Driver.h"
#include "Dwarf.h"
+#include "EhFrame.h"
#include "ExportTrie.h"
#include "InputSection.h"
#include "MachOStructs.h"
#include "SyntheticSections.h"
#include "Target.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/DWARF.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
#include "lld/Common/Reproduce.h"
#include "llvm/ADT/iterator.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/LTO/LTO.h"
+#include "llvm/Support/BinaryStreamReader.h"
#include "llvm/Support/Endian.h"
+#include "llvm/Support/LEB128.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TarWriter.h"
+#include "llvm/Support/TimeProfiler.h"
#include "llvm/TextAPI/Architecture.h"
#include "llvm/TextAPI/InterfaceFile.h"
+#include <optional>
+#include <type_traits>
+
using namespace llvm;
using namespace llvm::MachO;
using namespace llvm::support::endian;
return (f->archiveName + "(" + path::filename(f->getName()) + ")").str();
}
+std::string lld::toString(const Section &sec) {
+ return (toString(sec.file) + ":(" + sec.name + ")").str();
+}
+
SetVector<InputFile *> macho::inputFiles;
std::unique_ptr<TarWriter> macho::tar;
int InputFile::idCount = 0;
const char *hdr = input->mb.getBufferStart();
+ // "Zippered" object files can have multiple LC_BUILD_VERSION load commands.
std::vector<PlatformInfo> platformInfos;
for (auto *cmd : findCommands<build_version_command>(hdr, LC_BUILD_VERSION)) {
PlatformInfo info;
- info.target.Platform = static_cast<PlatformKind>(cmd->platform);
+ info.target.Platform = static_cast<PlatformType>(cmd->platform);
info.minimum = decodeVersion(cmd->minos);
platformInfos.emplace_back(std::move(info));
}
PlatformInfo info;
switch (cmd->cmd) {
case LC_VERSION_MIN_MACOSX:
- info.target.Platform = PlatformKind::macOS;
+ info.target.Platform = PLATFORM_MACOS;
break;
case LC_VERSION_MIN_IPHONEOS:
- info.target.Platform = PlatformKind::iOS;
+ info.target.Platform = PLATFORM_IOS;
break;
case LC_VERSION_MIN_TVOS:
- info.target.Platform = PlatformKind::tvOS;
+ info.target.Platform = PLATFORM_TVOS;
break;
case LC_VERSION_MIN_WATCHOS:
- info.target.Platform = PlatformKind::watchOS;
+ info.target.Platform = PLATFORM_WATCHOS;
break;
}
info.minimum = decodeVersion(cmd->version);
return true;
}
+// This cache mostly exists to store system libraries (and .tbds) as they're
+// loaded, rather than the input archives, which are already cached at a higher
+// level, and other files like the filelist that are only read once.
+// Theoretically this caching could be more efficient by hoisting it, but that
+// would require altering many callers to track the state.
+DenseMap<CachedHashStringRef, MemoryBufferRef> macho::cachedReads;
// Open a given file path and return it as a memory-mapped file.
-Optional<MemoryBufferRef> macho::readFile(StringRef path) {
+std::optional<MemoryBufferRef> macho::readFile(StringRef path) {
+ CachedHashStringRef key(path);
+ auto entry = cachedReads.find(key);
+ if (entry != cachedReads.end())
+ return entry->second;
+
ErrorOr<std::unique_ptr<MemoryBuffer>> mbOrErr = MemoryBuffer::getFile(path);
if (std::error_code ec = mbOrErr.getError()) {
error("cannot open " + path + ": " + ec.message());
- return None;
+ return std::nullopt;
}
std::unique_ptr<MemoryBuffer> &mb = *mbOrErr;
read32be(&hdr->magic) != FAT_MAGIC) {
if (tar)
tar->append(relativeToRoot(path), mbref.getBuffer());
- return mbref;
+ return cachedReads[key] = mbref;
}
+ llvm::BumpPtrAllocator &bAlloc = lld::bAlloc();
+
// Object files and archive files may be fat files, which contain multiple
// real files for different CPU ISAs. Here, we search for a file that matches
// with the current link target and returns it as a MemoryBufferRef.
const auto *arch = reinterpret_cast<const fat_arch *>(buf + sizeof(*hdr));
+ auto getArchName = [](uint32_t cpuType, uint32_t cpuSubtype) {
+ return getArchitectureName(getArchitectureFromCpuType(cpuType, cpuSubtype));
+ };
+ std::vector<StringRef> archs;
for (uint32_t i = 0, n = read32be(&hdr->nfat_arch); i < n; ++i) {
if (reinterpret_cast<const char *>(arch + i + 1) >
buf + mbref.getBufferSize()) {
error(path + ": fat_arch struct extends beyond end of file");
- return None;
+ return std::nullopt;
}
- if (read32be(&arch[i].cputype) != static_cast<uint32_t>(target->cpuType) ||
- read32be(&arch[i].cpusubtype) != target->cpuSubtype)
+ uint32_t cpuType = read32be(&arch[i].cputype);
+ uint32_t cpuSubtype =
+ read32be(&arch[i].cpusubtype) & ~MachO::CPU_SUBTYPE_MASK;
+
+ // FIXME: LD64 has a more complex fallback logic here.
+ // Consider implementing that as well?
+ if (cpuType != static_cast<uint32_t>(target->cpuType) ||
+ cpuSubtype != target->cpuSubtype) {
+ archs.emplace_back(getArchName(cpuType, cpuSubtype));
continue;
+ }
uint32_t offset = read32be(&arch[i].offset);
uint32_t size = read32be(&arch[i].size);
error(path + ": slice extends beyond end of file");
if (tar)
tar->append(relativeToRoot(path), mbref.getBuffer());
- return MemoryBufferRef(StringRef(buf + offset, size), path.copy(bAlloc));
+ return cachedReads[key] = MemoryBufferRef(StringRef(buf + offset, size),
+ path.copy(bAlloc));
}
- error("unable to find matching architecture in " + path);
- return None;
+ auto targetArchName = getArchName(target->cpuType, target->cpuSubtype);
+ warn(path + ": ignoring file because it is universal (" + join(archs, ",") +
+ ") but does not contain the " + targetArchName + " architecture");
+ return std::nullopt;
}
InputFile::InputFile(Kind kind, const InterfaceFile &interface)
- : id(idCount++), fileKind(kind), name(saver.save(interface.getPath())) {}
+ : id(idCount++), fileKind(kind), name(saver().save(interface.getPath())) {}
+
+// Some sections comprise of fixed-size records, so instead of splitting them at
+// symbol boundaries, we split them based on size. Records are distinct from
+// literals in that they may contain references to other sections, instead of
+// being leaf nodes in the InputSection graph.
+//
+// Note that "record" is a term I came up with. In contrast, "literal" is a term
+// used by the Mach-O format.
+static std::optional<size_t> getRecordSize(StringRef segname, StringRef name) {
+ if (name == section_names::compactUnwind) {
+ if (segname == segment_names::ld)
+ return target->wordSize == 8 ? 32 : 20;
+ }
+ if (!config->dedupStrings)
+ return {};
+
+ if (name == section_names::cfString && segname == segment_names::data)
+ return target->wordSize == 8 ? 32 : 16;
+
+ if (config->icfLevel == ICFLevel::none)
+ return {};
+
+ if (name == section_names::objcClassRefs && segname == segment_names::data)
+ return target->wordSize;
+
+ if (name == section_names::objcSelrefs && segname == segment_names::data)
+ return target->wordSize;
+ return {};
+}
-template <class Section>
-void ObjFile::parseSections(ArrayRef<Section> sections) {
- subsections.reserve(sections.size());
+static Error parseCallGraph(ArrayRef<uint8_t> data,
+ std::vector<CallGraphEntry> &callGraph) {
+ TimeTraceScope timeScope("Parsing call graph section");
+ BinaryStreamReader reader(data, support::little);
+ while (!reader.empty()) {
+ uint32_t fromIndex, toIndex;
+ uint64_t count;
+ if (Error err = reader.readInteger(fromIndex))
+ return err;
+ if (Error err = reader.readInteger(toIndex))
+ return err;
+ if (Error err = reader.readInteger(count))
+ return err;
+ callGraph.emplace_back(fromIndex, toIndex, count);
+ }
+ return Error::success();
+}
+
+// Parse the sequence of sections within a single LC_SEGMENT(_64).
+// Split each section into subsections.
+template <class SectionHeader>
+void ObjFile::parseSections(ArrayRef<SectionHeader> sectionHeaders) {
+ sections.reserve(sectionHeaders.size());
auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
- for (const Section &sec : sections) {
+ for (const SectionHeader &sec : sectionHeaders) {
StringRef name =
StringRef(sec.sectname, strnlen(sec.sectname, sizeof(sec.sectname)));
StringRef segname =
StringRef(sec.segname, strnlen(sec.segname, sizeof(sec.segname)));
- ArrayRef<uint8_t> data = {isZeroFill(sec.flags) ? nullptr
- : buf + sec.offset,
- static_cast<size_t>(sec.size)};
+ sections.push_back(make<Section>(this, segname, name, sec.flags, sec.addr));
if (sec.align >= 32) {
error("alignment " + std::to_string(sec.align) + " of section " + name +
" is too large");
- subsections.push_back({});
continue;
}
+ Section §ion = *sections.back();
uint32_t align = 1 << sec.align;
- uint32_t flags = sec.flags;
-
- if (sectionType(sec.flags) == S_CSTRING_LITERALS ||
- (config->dedupLiterals && isWordLiteralSection(sec.flags))) {
- if (sec.nreloc && config->dedupLiterals)
- fatal(toString(this) + " contains relocations in " + sec.segname + "," +
- sec.sectname +
- ", so LLD cannot deduplicate literals. Try re-running without "
- "--deduplicate-literals.");
-
- InputSection *isec;
- if (sectionType(sec.flags) == S_CSTRING_LITERALS) {
- isec =
- make<CStringInputSection>(segname, name, this, data, align, flags);
- // FIXME: parallelize this?
- cast<CStringInputSection>(isec)->splitIntoPieces();
- } else {
- isec = make<WordLiteralInputSection>(segname, name, this, data, align,
- flags);
+ ArrayRef<uint8_t> data = {isZeroFill(sec.flags) ? nullptr
+ : buf + sec.offset,
+ static_cast<size_t>(sec.size)};
+
+ auto splitRecords = [&](size_t recordSize) -> void {
+ if (data.empty())
+ return;
+ Subsections &subsections = section.subsections;
+ subsections.reserve(data.size() / recordSize);
+ for (uint64_t off = 0; off < data.size(); off += recordSize) {
+ auto *isec = make<ConcatInputSection>(
+ section, data.slice(off, std::min(data.size(), recordSize)), align);
+ subsections.push_back({off, isec});
}
- subsections.push_back({{0, isec}});
- } else if (config->icfLevel != ICFLevel::none &&
- (name == section_names::cfString &&
- segname == segment_names::data)) {
- uint64_t literalSize = target->wordSize == 8 ? 32 : 16;
- subsections.push_back({});
- SubsectionMap &subsecMap = subsections.back();
- for (uint64_t off = 0; off < data.size(); off += literalSize)
- subsecMap.push_back(
- {off, make<ConcatInputSection>(segname, name, this,
- data.slice(off, literalSize), align,
- flags)});
+ section.doneSplitting = true;
+ };
+
+ if (sectionType(sec.flags) == S_CSTRING_LITERALS) {
+ if (sec.nreloc)
+ fatal(toString(this) + ": " + sec.segname + "," + sec.sectname +
+ " contains relocations, which is unsupported");
+ bool dedupLiterals =
+ name == section_names::objcMethname || config->dedupStrings;
+ InputSection *isec =
+ make<CStringInputSection>(section, data, align, dedupLiterals);
+ // FIXME: parallelize this?
+ cast<CStringInputSection>(isec)->splitIntoPieces();
+ section.subsections.push_back({0, isec});
+ } else if (isWordLiteralSection(sec.flags)) {
+ if (sec.nreloc)
+ fatal(toString(this) + ": " + sec.segname + "," + sec.sectname +
+ " contains relocations, which is unsupported");
+ InputSection *isec = make<WordLiteralInputSection>(section, data, align);
+ section.subsections.push_back({0, isec});
+ } else if (auto recordSize = getRecordSize(segname, name)) {
+ splitRecords(*recordSize);
+ } else if (name == section_names::ehFrame &&
+ segname == segment_names::text) {
+ splitEhFrames(data, *sections.back());
+ } else if (segname == segment_names::llvm) {
+ if (config->callGraphProfileSort && name == section_names::cgProfile)
+ checkError(parseCallGraph(data, callGraph));
+ // ld64 does not appear to emit contents from sections within the __LLVM
+ // segment. Symbols within those sections point to bitcode metadata
+ // instead of actual symbols. Global symbols within those sections could
+ // have the same name without causing duplicate symbol errors. To avoid
+ // spurious duplicate symbol errors, we do not parse these sections.
+ // TODO: Evaluate whether the bitcode metadata is needed.
+ } else if (name == section_names::objCImageInfo &&
+ segname == segment_names::data) {
+ objCImageInfo = data;
} else {
- auto *isec =
- make<ConcatInputSection>(segname, name, this, data, align, flags);
- if (!(isDebugSection(isec->getFlags()) &&
- isec->getSegName() == segment_names::dwarf)) {
- subsections.push_back({{0, isec}});
- } else {
+ if (name == section_names::addrSig)
+ addrSigSection = sections.back();
+
+ auto *isec = make<ConcatInputSection>(section, data, align);
+ if (isDebugSection(isec->getFlags()) &&
+ isec->getSegName() == segment_names::dwarf) {
// Instead of emitting DWARF sections, we emit STABS symbols to the
// object files that contain them. We filter them out early to avoid
- // parsing their relocations unnecessarily. But we must still push an
- // empty map to ensure the indices line up for the remaining sections.
- subsections.push_back({});
+ // parsing their relocations unnecessarily.
debugSections.push_back(isec);
+ } else {
+ section.subsections.push_back({0, isec});
}
}
}
}
+void ObjFile::splitEhFrames(ArrayRef<uint8_t> data, Section &ehFrameSection) {
+ EhReader reader(this, data, /*dataOff=*/0);
+ size_t off = 0;
+ while (off < reader.size()) {
+ uint64_t frameOff = off;
+ uint64_t length = reader.readLength(&off);
+ if (length == 0)
+ break;
+ uint64_t fullLength = length + (off - frameOff);
+ off += length;
+ // We hard-code an alignment of 1 here because we don't actually want our
+ // EH frames to be aligned to the section alignment. EH frame decoders don't
+ // expect this alignment. Moreover, each EH frame must start where the
+ // previous one ends, and where it ends is indicated by the length field.
+ // Unless we update the length field (troublesome), we should keep the
+ // alignment to 1.
+ // Note that we still want to preserve the alignment of the overall section,
+ // just not of the individual EH frames.
+ ehFrameSection.subsections.push_back(
+ {frameOff, make<ConcatInputSection>(ehFrameSection,
+ data.slice(frameOff, fullLength),
+ /*align=*/1)});
+ }
+ ehFrameSection.doneSplitting = true;
+}
+
+template <class T>
+static Section *findContainingSection(const std::vector<Section *> §ions,
+ T *offset) {
+ static_assert(std::is_same<uint64_t, T>::value ||
+ std::is_same<uint32_t, T>::value,
+ "unexpected type for offset");
+ auto it = std::prev(llvm::upper_bound(
+ sections, *offset,
+ [](uint64_t value, const Section *sec) { return value < sec->addr; }));
+ *offset -= (*it)->addr;
+ return *it;
+}
+
// Find the subsection corresponding to the greatest section offset that is <=
// that of the given offset.
//
// any subsection splitting has occurred). It will be updated to represent the
// same location as an offset relative to the start of the containing
// subsection.
-static InputSection *findContainingSubsection(SubsectionMap &map,
- uint64_t *offset) {
+template <class T>
+static InputSection *findContainingSubsection(const Section §ion,
+ T *offset) {
+ static_assert(std::is_same<uint64_t, T>::value ||
+ std::is_same<uint32_t, T>::value,
+ "unexpected type for offset");
auto it = std::prev(llvm::upper_bound(
- map, *offset, [](uint64_t value, SubsectionEntry subsecEntry) {
- return value < subsecEntry.offset;
- }));
+ section.subsections, *offset,
+ [](uint64_t value, Subsection subsec) { return value < subsec.offset; }));
*offset -= it->offset;
return it->isec;
}
-template <class Section>
-static bool validateRelocationInfo(InputFile *file, const Section &sec,
+// Find a symbol at offset `off` within `isec`.
+static Defined *findSymbolAtOffset(const ConcatInputSection *isec,
+ uint64_t off) {
+ auto it = llvm::lower_bound(isec->symbols, off, [](Defined *d, uint64_t off) {
+ return d->value < off;
+ });
+ // The offset should point at the exact address of a symbol (with no addend.)
+ if (it == isec->symbols.end() || (*it)->value != off) {
+ assert(isec->wasCoalesced);
+ return nullptr;
+ }
+ return *it;
+}
+
+template <class SectionHeader>
+static bool validateRelocationInfo(InputFile *file, const SectionHeader &sec,
relocation_info rel) {
const RelocAttrs &relocAttrs = target->getRelocAttrs(rel.r_type);
bool valid = true;
return valid;
}
-template <class Section>
-void ObjFile::parseRelocations(ArrayRef<Section> sectionHeaders,
- const Section &sec, SubsectionMap &subsecMap) {
+template <class SectionHeader>
+void ObjFile::parseRelocations(ArrayRef<SectionHeader> sectionHeaders,
+ const SectionHeader &sec, Section §ion) {
auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
ArrayRef<relocation_info> relInfos(
reinterpret_cast<const relocation_info *>(buf + sec.reloff), sec.nreloc);
- auto subsecIt = subsecMap.rbegin();
+ Subsections &subsections = section.subsections;
+ auto subsecIt = subsections.rbegin();
for (size_t i = 0; i < relInfos.size(); i++) {
// Paired relocations serve as Mach-O's method for attaching a
// supplemental datum to a primary relocation record. ELF does not
// ARM64_RELOC_BRANCH26 or ARM64_RELOC_PAGE21/PAGEOFF12 holds the
// base symbolic address.
//
- // Note: X86 does not use *_RELOC_ADDEND because it can embed an
- // addend into the instruction stream. On X86, a relocatable address
- // field always occupies an entire contiguous sequence of byte(s),
- // so there is no need to merge opcode bits with address
- // bits. Therefore, it's easy and convenient to store addends in the
- // instruction-stream bytes that would otherwise contain zeroes. By
- // contrast, RISC ISAs such as ARM64 mix opcode bits with with
- // address bits so that bitwise arithmetic is necessary to extract
- // and insert them. Storing addends in the instruction stream is
- // possible, but inconvenient and more costly at link time.
+ // Note: X86 does not use *_RELOC_ADDEND because it can embed an addend into
+ // the instruction stream. On X86, a relocatable address field always
+ // occupies an entire contiguous sequence of byte(s), so there is no need to
+ // merge opcode bits with address bits. Therefore, it's easy and convenient
+ // to store addends in the instruction-stream bytes that would otherwise
+ // contain zeroes. By contrast, RISC ISAs such as ARM64 mix opcode bits with
+ // address bits so that bitwise arithmetic is necessary to extract and
+ // insert them. Storing addends in the instruction stream is possible, but
+ // inconvenient and more costly at link time.
- int64_t pairedAddend = 0;
relocation_info relInfo = relInfos[i];
+ bool isSubtrahend =
+ target->hasAttr(relInfo.r_type, RelocAttrBits::SUBTRAHEND);
+ int64_t pairedAddend = 0;
if (target->hasAttr(relInfo.r_type, RelocAttrBits::ADDEND)) {
pairedAddend = SignExtend64<24>(relInfo.r_symbolnum);
relInfo = relInfos[++i];
if (relInfo.r_address & R_SCATTERED)
fatal("TODO: Scattered relocations not supported");
- bool isSubtrahend =
- target->hasAttr(relInfo.r_type, RelocAttrBits::SUBTRAHEND);
int64_t embeddedAddend = target->getEmbeddedAddend(mb, sec.offset, relInfo);
assert(!(embeddedAddend && pairedAddend));
int64_t totalAddend = pairedAddend + embeddedAddend;
r.addend = isSubtrahend ? 0 : totalAddend;
} else {
assert(!isSubtrahend);
- const Section &referentSec = sectionHeaders[relInfo.r_symbolnum - 1];
+ const SectionHeader &referentSecHead =
+ sectionHeaders[relInfo.r_symbolnum - 1];
uint64_t referentOffset;
if (relInfo.r_pcrel) {
// The implicit addend for pcrel section relocations is the pcrel offset
// have pcrel section relocations. We may want to factor this out into
// the arch-specific .cpp file.
assert(target->hasAttr(r.type, RelocAttrBits::BYTE4));
- referentOffset =
- sec.addr + relInfo.r_address + 4 + totalAddend - referentSec.addr;
+ referentOffset = sec.addr + relInfo.r_address + 4 + totalAddend -
+ referentSecHead.addr;
} else {
// The addend for a non-pcrel relocation is its absolute address.
- referentOffset = totalAddend - referentSec.addr;
+ referentOffset = totalAddend - referentSecHead.addr;
}
- SubsectionMap &referentSubsecMap = subsections[relInfo.r_symbolnum - 1];
- r.referent = findContainingSubsection(referentSubsecMap, &referentOffset);
+ r.referent = findContainingSubsection(*sections[relInfo.r_symbolnum - 1],
+ &referentOffset);
r.addend = referentOffset;
}
// unsorted relocations (in `-r` mode), so we have a fallback for that
// uncommon case.
InputSection *subsec;
- while (subsecIt != subsecMap.rend() && subsecIt->offset > r.offset)
+ while (subsecIt != subsections.rend() && subsecIt->offset > r.offset)
++subsecIt;
- if (subsecIt == subsecMap.rend() ||
+ if (subsecIt == subsections.rend() ||
subsecIt->offset + subsecIt->isec->getSize() <= r.offset) {
- subsec = findContainingSubsection(subsecMap, &r.offset);
+ subsec = findContainingSubsection(section, &r.offset);
// Now that we know the relocs are unsorted, avoid trying the 'fast path'
// for the other relocations.
- subsecIt = subsecMap.rend();
+ subsecIt = subsections.rend();
} else {
subsec = subsecIt->isec;
r.offset -= subsecIt->offset;
} else {
uint64_t referentOffset =
totalAddend - sectionHeaders[minuendInfo.r_symbolnum - 1].addr;
- SubsectionMap &referentSubsecMap =
- subsections[minuendInfo.r_symbolnum - 1];
- p.referent =
- findContainingSubsection(referentSubsecMap, &referentOffset);
+ p.referent = findContainingSubsection(
+ *sections[minuendInfo.r_symbolnum - 1], &referentOffset);
p.addend = referentOffset;
}
subsec->relocs.push_back(p);
}
}
+// Symbols with `l` or `L` as a prefix are linker-private and never appear in
+// the output.
+static bool isPrivateLabel(StringRef name) {
+ return name.startswith("l") || name.startswith("L");
+}
+
template <class NList>
static macho::Symbol *createDefined(const NList &sym, StringRef name,
InputSection *isec, uint64_t value,
- uint64_t size) {
+ uint64_t size, bool forceHidden) {
// Symbol scope is determined by sym.n_type & (N_EXT | N_PEXT):
// N_EXT: Global symbols. These go in the symbol table during the link,
// and also in the export table of the output so that the dynamic
(sym.n_desc & (N_WEAK_DEF | N_WEAK_REF)) == (N_WEAK_DEF | N_WEAK_REF);
if (sym.n_type & N_EXT) {
- bool isPrivateExtern = sym.n_type & N_PEXT;
+ // -load_hidden makes us treat global symbols as linkage unit scoped.
+ // Duplicates are reported but the symbol does not go in the export trie.
+ bool isPrivateExtern = sym.n_type & N_PEXT || forceHidden;
+
// lld's behavior for merging symbols is slightly different from ld64:
// ld64 picks the winning symbol based on several criteria (see
// pickBetweenRegularAtoms() in ld64's SymbolTable.cpp), while lld
// with ld64's semantics, because it means the non-private-extern
// definition will continue to take priority if more private extern
// definitions are encountered. With lld's semantics there's no observable
- // difference between a symbol that's isWeakDefCanBeHidden or one that's
- // privateExtern -- neither makes it into the dynamic symbol table. So just
- // promote isWeakDefCanBeHidden to isPrivateExtern here.
- if (isWeakDefCanBeHidden)
+ // difference between a symbol that's isWeakDefCanBeHidden(autohide) or one
+ // that's privateExtern -- neither makes it into the dynamic symbol table,
+ // unless the autohide symbol is explicitly exported.
+ // But if a symbol is both privateExtern and autohide then it can't
+ // be exported.
+ // So we nullify the autohide flag when privateExtern is present
+ // and promote the symbol to privateExtern when it is not already.
+ if (isWeakDefCanBeHidden && isPrivateExtern)
+ isWeakDefCanBeHidden = false;
+ else if (isWeakDefCanBeHidden)
isPrivateExtern = true;
-
return symtab->addDefined(
name, isec->getFile(), isec, value, size, sym.n_desc & N_WEAK_DEF,
isPrivateExtern, sym.n_desc & N_ARM_THUMB_DEF,
- sym.n_desc & REFERENCED_DYNAMICALLY, sym.n_desc & N_NO_DEAD_STRIP);
+ sym.n_desc & REFERENCED_DYNAMICALLY, sym.n_desc & N_NO_DEAD_STRIP,
+ isWeakDefCanBeHidden);
}
-
- assert(!isWeakDefCanBeHidden &&
- "weak_def_can_be_hidden on already-hidden symbol?");
+ bool includeInSymtab = !isPrivateLabel(name) && !isEhFrameSection(isec);
return make<Defined>(
name, isec->getFile(), isec, value, size, sym.n_desc & N_WEAK_DEF,
- /*isExternal=*/false, /*isPrivateExtern=*/false,
+ /*isExternal=*/false, /*isPrivateExtern=*/false, includeInSymtab,
sym.n_desc & N_ARM_THUMB_DEF, sym.n_desc & REFERENCED_DYNAMICALLY,
sym.n_desc & N_NO_DEAD_STRIP);
}
// InputSection. They cannot be weak.
template <class NList>
static macho::Symbol *createAbsolute(const NList &sym, InputFile *file,
- StringRef name) {
+ StringRef name, bool forceHidden) {
if (sym.n_type & N_EXT) {
+ bool isPrivateExtern = sym.n_type & N_PEXT || forceHidden;
return symtab->addDefined(
name, file, nullptr, sym.n_value, /*size=*/0,
- /*isWeakDef=*/false, sym.n_type & N_PEXT, sym.n_desc & N_ARM_THUMB_DEF,
- /*isReferencedDynamically=*/false, sym.n_desc & N_NO_DEAD_STRIP);
+ /*isWeakDef=*/false, isPrivateExtern, sym.n_desc & N_ARM_THUMB_DEF,
+ /*isReferencedDynamically=*/false, sym.n_desc & N_NO_DEAD_STRIP,
+ /*isWeakDefCanBeHidden=*/false);
}
return make<Defined>(name, file, nullptr, sym.n_value, /*size=*/0,
/*isWeakDef=*/false,
/*isExternal=*/false, /*isPrivateExtern=*/false,
- sym.n_desc & N_ARM_THUMB_DEF,
+ /*includeInSymtab=*/true, sym.n_desc & N_ARM_THUMB_DEF,
/*isReferencedDynamically=*/false,
sym.n_desc & N_NO_DEAD_STRIP);
}
template <class NList>
macho::Symbol *ObjFile::parseNonSectionSymbol(const NList &sym,
- StringRef name) {
+ const char *strtab) {
+ StringRef name = StringRef(strtab + sym.n_strx);
uint8_t type = sym.n_type & N_TYPE;
+ bool isPrivateExtern = sym.n_type & N_PEXT || forceHidden;
switch (type) {
case N_UNDF:
return sym.n_value == 0
? symtab->addUndefined(name, this, sym.n_desc & N_WEAK_REF)
: symtab->addCommon(name, this, sym.n_value,
1 << GET_COMM_ALIGN(sym.n_desc),
- sym.n_type & N_PEXT);
+ isPrivateExtern);
case N_ABS:
- return createAbsolute(sym, this, name);
+ return createAbsolute(sym, this, name, forceHidden);
+ case N_INDR: {
+ // Not much point in making local aliases -- relocs in the current file can
+ // just refer to the actual symbol itself. ld64 ignores these symbols too.
+ if (!(sym.n_type & N_EXT))
+ return nullptr;
+ StringRef aliasedName = StringRef(strtab + sym.n_value);
+ // isPrivateExtern is the only symbol flag that has an impact on the final
+ // aliased symbol.
+ auto alias = make<AliasSymbol>(this, name, aliasedName, isPrivateExtern);
+ aliases.push_back(alias);
+ return alias;
+ }
case N_PBUD:
- case N_INDR:
- error("TODO: support symbols of type " + std::to_string(type));
+ error("TODO: support symbols of type N_PBUD");
return nullptr;
case N_SECT:
llvm_unreachable(
}
}
-template <class NList>
-static bool isUndef(const NList &sym) {
+template <class NList> static bool isUndef(const NList &sym) {
return (sym.n_type & N_TYPE) == N_UNDF && sym.n_value == 0;
}
using NList = typename LP::nlist;
// Groups indices of the symbols by the sections that contain them.
- std::vector<std::vector<uint32_t>> symbolsBySection(subsections.size());
+ std::vector<std::vector<uint32_t>> symbolsBySection(sections.size());
symbols.resize(nList.size());
SmallVector<unsigned, 32> undefineds;
for (uint32_t i = 0; i < nList.size(); ++i) {
if (sym.n_type & N_STAB)
continue;
- StringRef name = strtab + sym.n_strx;
if ((sym.n_type & N_TYPE) == N_SECT) {
- SubsectionMap &subsecMap = subsections[sym.n_sect - 1];
+ Subsections &subsections = sections[sym.n_sect - 1]->subsections;
// parseSections() may have chosen not to parse this section.
- if (subsecMap.empty())
+ if (subsections.empty())
continue;
symbolsBySection[sym.n_sect - 1].push_back(i);
} else if (isUndef(sym)) {
undefineds.push_back(i);
} else {
- symbols[i] = parseNonSectionSymbol(sym, name);
+ symbols[i] = parseNonSectionSymbol(sym, strtab);
}
}
- for (size_t i = 0; i < subsections.size(); ++i) {
- SubsectionMap &subsecMap = subsections[i];
- if (subsecMap.empty())
+ for (size_t i = 0; i < sections.size(); ++i) {
+ Subsections &subsections = sections[i]->subsections;
+ if (subsections.empty())
continue;
-
std::vector<uint32_t> &symbolIndices = symbolsBySection[i];
uint64_t sectionAddr = sectionHeaders[i].addr;
uint32_t sectionAlign = 1u << sectionHeaders[i].align;
- InputSection *isec = subsecMap.back().isec;
- // __cfstring has already been split into subsections during
+ // Some sections have already been split into subsections during
// parseSections(), so we simply need to match Symbols to the corresponding
// subsection here.
- if (config->icfLevel != ICFLevel::none && isCfStringSection(isec)) {
+ if (sections[i]->doneSplitting) {
for (size_t j = 0; j < symbolIndices.size(); ++j) {
- uint32_t symIndex = symbolIndices[j];
+ const uint32_t symIndex = symbolIndices[j];
const NList &sym = nList[symIndex];
StringRef name = strtab + sym.n_strx;
uint64_t symbolOffset = sym.n_value - sectionAddr;
- InputSection *isec = findContainingSubsection(subsecMap, &symbolOffset);
+ InputSection *isec =
+ findContainingSubsection(*sections[i], &symbolOffset);
if (symbolOffset != 0) {
- error(toString(this) + ": __cfstring contains symbol " + name +
+ error(toString(*sections[i]) + ": symbol " + name +
" at misaligned offset");
continue;
}
- symbols[symIndex] = createDefined(sym, name, isec, 0, isec->getSize());
+ symbols[symIndex] =
+ createDefined(sym, name, isec, 0, isec->getSize(), forceHidden);
}
continue;
}
+ sections[i]->doneSplitting = true;
+
+ auto getSymName = [strtab](const NList& sym) -> StringRef {
+ return StringRef(strtab + sym.n_strx);
+ };
// Calculate symbol sizes and create subsections by splitting the sections
// along symbol boundaries.
- // We populate subsecMap by repeatedly splitting the last (highest address)
- // subsection.
+ // We populate subsections by repeatedly splitting the last (highest
+ // address) subsection.
llvm::stable_sort(symbolIndices, [&](uint32_t lhs, uint32_t rhs) {
+ // Put private-label symbols that have no flags after other symbols at the
+ // same address.
+ StringRef lhsName = getSymName(nList[lhs]);
+ StringRef rhsName = getSymName(nList[rhs]);
+ if (nList[lhs].n_value == nList[rhs].n_value) {
+ if (isPrivateLabel(lhsName) && isPrivateLabel(rhsName))
+ return nList[lhs].n_desc > nList[rhs].n_desc;
+ return !isPrivateLabel(lhsName) && isPrivateLabel(rhsName);
+ }
return nList[lhs].n_value < nList[rhs].n_value;
});
- SubsectionEntry subsecEntry = subsecMap.back();
for (size_t j = 0; j < symbolIndices.size(); ++j) {
- uint32_t symIndex = symbolIndices[j];
+ const uint32_t symIndex = symbolIndices[j];
const NList &sym = nList[symIndex];
- StringRef name = strtab + sym.n_strx;
- InputSection *isec = subsecEntry.isec;
+ StringRef name = getSymName(sym);
+ Subsection &subsec = subsections.back();
+ InputSection *isec = subsec.isec;
- uint64_t subsecAddr = sectionAddr + subsecEntry.offset;
+ uint64_t subsecAddr = sectionAddr + subsec.offset;
size_t symbolOffset = sym.n_value - subsecAddr;
uint64_t symbolSize =
j + 1 < symbolIndices.size()
// 4. If we have a literal section (e.g. __cstring and __literal4).
if (!subsectionsViaSymbols || symbolOffset == 0 ||
sym.n_desc & N_ALT_ENTRY || !isa<ConcatInputSection>(isec)) {
- symbols[symIndex] =
- createDefined(sym, name, isec, symbolOffset, symbolSize);
+ isec->hasAltEntry = symbolOffset != 0;
+ // If we have an private-label symbol that's an alias, and that alias
+ // doesn't have any flags of its own, then we can just reuse the aliased
+ // symbol. Our sorting step above ensures that any such symbols will
+ // appear after the non-private-label ones. See weak-def-alias-ignored.s
+ // for the motivation behind this.
+ if (symbolOffset == 0 && isPrivateLabel(name) && j != 0 &&
+ sym.n_desc == 0)
+ symbols[symIndex] = symbols[symbolIndices[j - 1]];
+ else
+ symbols[symIndex] = createDefined(sym, name, isec, symbolOffset,
+ symbolSize, forceHidden);
continue;
}
auto *concatIsec = cast<ConcatInputSection>(isec);
auto *nextIsec = make<ConcatInputSection>(*concatIsec);
- nextIsec->numRefs = 0;
nextIsec->wasCoalesced = false;
if (isZeroFill(isec->getFlags())) {
// Zero-fill sections have NULL data.data() non-zero data.size()
// By construction, the symbol will be at offset zero in the new
// subsection.
- symbols[symIndex] =
- createDefined(sym, name, nextIsec, /*value=*/0, symbolSize);
+ symbols[symIndex] = createDefined(sym, name, nextIsec, /*value=*/0,
+ symbolSize, forceHidden);
// TODO: ld64 appears to preserve the original alignment as well as each
// subsection's offset from the last aligned address. We should consider
// emulating that behavior.
nextIsec->align = MinAlign(sectionAlign, sym.n_value);
- subsecMap.push_back({sym.n_value - sectionAddr, nextIsec});
- subsecEntry = subsecMap.back();
+ subsections.push_back({sym.n_value - sectionAddr, nextIsec});
}
}
// symbol resolution behavior. In addition, a set of interconnected symbols
// will all be resolved to the same file, instead of being resolved to
// different files.
- for (unsigned i : undefineds) {
- const NList &sym = nList[i];
- StringRef name = strtab + sym.n_strx;
- symbols[i] = parseNonSectionSymbol(sym, name);
- }
+ for (unsigned i : undefineds)
+ symbols[i] = parseNonSectionSymbol(nList[i], strtab);
}
OpaqueFile::OpaqueFile(MemoryBufferRef mb, StringRef segName,
: InputFile(OpaqueKind, mb) {
const auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
ArrayRef<uint8_t> data = {buf, mb.getBufferSize()};
- ConcatInputSection *isec =
- make<ConcatInputSection>(segName.take_front(16), sectName.take_front(16),
- /*file=*/this, data);
+ sections.push_back(make<Section>(/*file=*/this, segName.take_front(16),
+ sectName.take_front(16),
+ /*flags=*/0, /*addr=*/0));
+ Section §ion = *sections.back();
+ ConcatInputSection *isec = make<ConcatInputSection>(section, data);
isec->live = true;
- subsections.push_back({{0, isec}});
+ section.subsections.push_back({0, isec});
}
-ObjFile::ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName)
- : InputFile(ObjKind, mb), modTime(modTime) {
+ObjFile::ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName,
+ bool lazy, bool forceHidden)
+ : InputFile(ObjKind, mb, lazy), modTime(modTime), forceHidden(forceHidden) {
this->archiveName = std::string(archiveName);
- if (target->wordSize == 8)
- parse<LP64>();
- else
- parse<ILP32>();
+ if (lazy) {
+ if (target->wordSize == 8)
+ parseLazy<LP64>();
+ else
+ parseLazy<ILP32>();
+ } else {
+ if (target->wordSize == 8)
+ parse<LP64>();
+ else
+ parse<ILP32>();
+ }
}
template <class LP> void ObjFile::parse() {
using Header = typename LP::mach_header;
using SegmentCommand = typename LP::segment_command;
- using Section = typename LP::section;
+ using SectionHeader = typename LP::section;
using NList = typename LP::nlist;
auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
auto *hdr = reinterpret_cast<const Header *>(mb.getBufferStart());
- Architecture arch = getArchitectureFromCpuType(hdr->cputype, hdr->cpusubtype);
- if (arch != config->arch()) {
- error(toString(this) + " has architecture " + getArchitectureName(arch) +
- " which is incompatible with target architecture " +
- getArchitectureName(config->arch()));
+ uint32_t cpuType;
+ std::tie(cpuType, std::ignore) = getCPUTypeFromArchitecture(config->arch());
+ if (hdr->cputype != cpuType) {
+ Architecture arch =
+ getArchitectureFromCpuType(hdr->cputype, hdr->cpusubtype);
+ auto msg = config->errorForArchMismatch
+ ? static_cast<void (*)(const Twine &)>(error)
+ : warn;
+ msg(toString(this) + " has architecture " + getArchitectureName(arch) +
+ " which is incompatible with target architecture " +
+ getArchitectureName(config->arch()));
return;
}
parseLCLinkerOption(this, cmd->count, data);
}
- ArrayRef<Section> sectionHeaders;
+ ArrayRef<SectionHeader> sectionHeaders;
if (const load_command *cmd = findCommand(hdr, LP::segmentLCType)) {
auto *c = reinterpret_cast<const SegmentCommand *>(cmd);
- sectionHeaders =
- ArrayRef<Section>{reinterpret_cast<const Section *>(c + 1), c->nsects};
+ sectionHeaders = ArrayRef<SectionHeader>{
+ reinterpret_cast<const SectionHeader *>(c + 1), c->nsects};
parseSections(sectionHeaders);
}
// The relocations may refer to the symbols, so we parse them after we have
// parsed all the symbols.
- for (size_t i = 0, n = subsections.size(); i < n; ++i)
- if (!subsections[i].empty())
- parseRelocations(sectionHeaders, sectionHeaders[i], subsections[i]);
+ for (size_t i = 0, n = sections.size(); i < n; ++i)
+ if (!sections[i]->subsections.empty())
+ parseRelocations(sectionHeaders, sectionHeaders[i], *sections[i]);
parseDebugInfo();
- if (config->emitDataInCodeInfo)
- parseDataInCode();
+
+ Section *ehFrameSection = nullptr;
+ Section *compactUnwindSection = nullptr;
+ for (Section *sec : sections) {
+ Section **s = StringSwitch<Section **>(sec->name)
+ .Case(section_names::compactUnwind, &compactUnwindSection)
+ .Case(section_names::ehFrame, &ehFrameSection)
+ .Default(nullptr);
+ if (s)
+ *s = sec;
+ }
+ if (compactUnwindSection)
+ registerCompactUnwind(*compactUnwindSection);
+ if (ehFrameSection)
+ registerEhFrames(*ehFrameSection);
+}
+
+template <class LP> void ObjFile::parseLazy() {
+ using Header = typename LP::mach_header;
+ using NList = typename LP::nlist;
+
+ auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
+ auto *hdr = reinterpret_cast<const Header *>(mb.getBufferStart());
+ const load_command *cmd = findCommand(hdr, LC_SYMTAB);
+ if (!cmd)
+ return;
+ auto *c = reinterpret_cast<const symtab_command *>(cmd);
+ ArrayRef<NList> nList(reinterpret_cast<const NList *>(buf + c->symoff),
+ c->nsyms);
+ const char *strtab = reinterpret_cast<const char *>(buf) + c->stroff;
+ symbols.resize(nList.size());
+ for (const auto &[i, sym] : llvm::enumerate(nList)) {
+ if ((sym.n_type & N_EXT) && !isUndef(sym)) {
+ // TODO: Bound checking
+ StringRef name = strtab + sym.n_strx;
+ symbols[i] = symtab->addLazyObject(name, *this);
+ if (!lazy)
+ break;
+ }
+ }
}
void ObjFile::parseDebugInfo() {
if (!dObj)
return;
+ // We do not re-use the context from getDwarf() here as that function
+ // constructs an expensive DWARFCache object.
auto *ctx = make<DWARFContext>(
std::move(dObj), "",
[&](Error err) {
// FIXME: There can be more than one compile unit per object file. See
// PR48637.
auto it = units.begin();
- compileUnit = it->get();
+ compileUnit = it != units.end() ? it->get() : nullptr;
}
-void ObjFile::parseDataInCode() {
+ArrayRef<data_in_code_entry> ObjFile::getDataInCode() const {
const auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
const load_command *cmd = findCommand(buf, LC_DATA_IN_CODE);
if (!cmd)
- return;
+ return {};
const auto *c = reinterpret_cast<const linkedit_data_command *>(cmd);
- dataInCodeEntries = {
- reinterpret_cast<const data_in_code_entry *>(buf + c->dataoff),
- c->datasize / sizeof(data_in_code_entry)};
- assert(is_sorted(dataInCodeEntries, [](const data_in_code_entry &lhs,
- const data_in_code_entry &rhs) {
- return lhs.offset < rhs.offset;
- }));
+ return {reinterpret_cast<const data_in_code_entry *>(buf + c->dataoff),
+ c->datasize / sizeof(data_in_code_entry)};
+}
+
+ArrayRef<uint8_t> ObjFile::getOptimizationHints() const {
+ const auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
+ if (auto *cmd =
+ findCommand<linkedit_data_command>(buf, LC_LINKER_OPTIMIZATION_HINT))
+ return {buf + cmd->dataoff, cmd->datasize};
+ return {};
}
+// Create pointers from symbols to their associated compact unwind entries.
+void ObjFile::registerCompactUnwind(Section &compactUnwindSection) {
+ for (const Subsection &subsection : compactUnwindSection.subsections) {
+ ConcatInputSection *isec = cast<ConcatInputSection>(subsection.isec);
+ // Hack!! Each compact unwind entry (CUE) has its UNSIGNED relocations embed
+ // their addends in its data. Thus if ICF operated naively and compared the
+ // entire contents of each CUE, entries with identical unwind info but e.g.
+ // belonging to different functions would never be considered equivalent. To
+ // work around this problem, we remove some parts of the data containing the
+ // embedded addends. In particular, we remove the function address and LSDA
+ // pointers. Since these locations are at the start and end of the entry,
+ // we can do this using a simple, efficient slice rather than performing a
+ // copy. We are not losing any information here because the embedded
+ // addends have already been parsed in the corresponding Reloc structs.
+ //
+ // Removing these pointers would not be safe if they were pointers to
+ // absolute symbols. In that case, there would be no corresponding
+ // relocation. However, (AFAIK) MC cannot emit references to absolute
+ // symbols for either the function address or the LSDA. However, it *can* do
+ // so for the personality pointer, so we are not slicing that field away.
+ //
+ // Note that we do not adjust the offsets of the corresponding relocations;
+ // instead, we rely on `relocateCompactUnwind()` to correctly handle these
+ // truncated input sections.
+ isec->data = isec->data.slice(target->wordSize, 8 + target->wordSize);
+ uint32_t encoding = read32le(isec->data.data() + sizeof(uint32_t));
+ // llvm-mc omits CU entries for functions that need DWARF encoding, but
+ // `ld -r` doesn't. We can ignore them because we will re-synthesize these
+ // CU entries from the DWARF info during the output phase.
+ if ((encoding & static_cast<uint32_t>(UNWIND_MODE_MASK)) ==
+ target->modeDwarfEncoding)
+ continue;
+
+ ConcatInputSection *referentIsec;
+ for (auto it = isec->relocs.begin(); it != isec->relocs.end();) {
+ Reloc &r = *it;
+ // CUE::functionAddress is at offset 0. Skip personality & LSDA relocs.
+ if (r.offset != 0) {
+ ++it;
+ continue;
+ }
+ uint64_t add = r.addend;
+ if (auto *sym = cast_or_null<Defined>(r.referent.dyn_cast<Symbol *>())) {
+ // Check whether the symbol defined in this file is the prevailing one.
+ // Skip if it is e.g. a weak def that didn't prevail.
+ if (sym->getFile() != this) {
+ ++it;
+ continue;
+ }
+ add += sym->value;
+ referentIsec = cast<ConcatInputSection>(sym->isec);
+ } else {
+ referentIsec =
+ cast<ConcatInputSection>(r.referent.dyn_cast<InputSection *>());
+ }
+ // Unwind info lives in __DATA, and finalization of __TEXT will occur
+ // before finalization of __DATA. Moreover, the finalization of unwind
+ // info depends on the exact addresses that it references. So it is safe
+ // for compact unwind to reference addresses in __TEXT, but not addresses
+ // in any other segment.
+ if (referentIsec->getSegName() != segment_names::text)
+ error(isec->getLocation(r.offset) + " references section " +
+ referentIsec->getName() + " which is not in segment __TEXT");
+ // The functionAddress relocations are typically section relocations.
+ // However, unwind info operates on a per-symbol basis, so we search for
+ // the function symbol here.
+ Defined *d = findSymbolAtOffset(referentIsec, add);
+ if (!d) {
+ ++it;
+ continue;
+ }
+ d->unwindEntry = isec;
+ // Now that the symbol points to the unwind entry, we can remove the reloc
+ // that points from the unwind entry back to the symbol.
+ //
+ // First, the symbol keeps the unwind entry alive (and not vice versa), so
+ // this keeps dead-stripping simple.
+ //
+ // Moreover, it reduces the work that ICF needs to do to figure out if
+ // functions with unwind info are foldable.
+ //
+ // However, this does make it possible for ICF to fold CUEs that point to
+ // distinct functions (if the CUEs are otherwise identical).
+ // UnwindInfoSection takes care of this by re-duplicating the CUEs so that
+ // each one can hold a distinct functionAddress value.
+ //
+ // Given that clang emits relocations in reverse order of address, this
+ // relocation should be at the end of the vector for most of our input
+ // object files, so this erase() is typically an O(1) operation.
+ it = isec->relocs.erase(it);
+ }
+ }
+}
+
+struct CIE {
+ macho::Symbol *personalitySymbol = nullptr;
+ bool fdesHaveAug = false;
+ uint8_t lsdaPtrSize = 0; // 0 => no LSDA
+ uint8_t funcPtrSize = 0;
+};
+
+static uint8_t pointerEncodingToSize(uint8_t enc) {
+ switch (enc & 0xf) {
+ case dwarf::DW_EH_PE_absptr:
+ return target->wordSize;
+ case dwarf::DW_EH_PE_sdata4:
+ return 4;
+ case dwarf::DW_EH_PE_sdata8:
+ // ld64 doesn't actually support sdata8, but this seems simple enough...
+ return 8;
+ default:
+ return 0;
+ };
+}
+
+static CIE parseCIE(const InputSection *isec, const EhReader &reader,
+ size_t off) {
+ // Handling the full generality of possible DWARF encodings would be a major
+ // pain. We instead take advantage of our knowledge of how llvm-mc encodes
+ // DWARF and handle just that.
+ constexpr uint8_t expectedPersonalityEnc =
+ dwarf::DW_EH_PE_pcrel | dwarf::DW_EH_PE_indirect | dwarf::DW_EH_PE_sdata4;
+
+ CIE cie;
+ uint8_t version = reader.readByte(&off);
+ if (version != 1 && version != 3)
+ fatal("Expected CIE version of 1 or 3, got " + Twine(version));
+ StringRef aug = reader.readString(&off);
+ reader.skipLeb128(&off); // skip code alignment
+ reader.skipLeb128(&off); // skip data alignment
+ reader.skipLeb128(&off); // skip return address register
+ reader.skipLeb128(&off); // skip aug data length
+ uint64_t personalityAddrOff = 0;
+ for (char c : aug) {
+ switch (c) {
+ case 'z':
+ cie.fdesHaveAug = true;
+ break;
+ case 'P': {
+ uint8_t personalityEnc = reader.readByte(&off);
+ if (personalityEnc != expectedPersonalityEnc)
+ reader.failOn(off, "unexpected personality encoding 0x" +
+ Twine::utohexstr(personalityEnc));
+ personalityAddrOff = off;
+ off += 4;
+ break;
+ }
+ case 'L': {
+ uint8_t lsdaEnc = reader.readByte(&off);
+ cie.lsdaPtrSize = pointerEncodingToSize(lsdaEnc);
+ if (cie.lsdaPtrSize == 0)
+ reader.failOn(off, "unexpected LSDA encoding 0x" +
+ Twine::utohexstr(lsdaEnc));
+ break;
+ }
+ case 'R': {
+ uint8_t pointerEnc = reader.readByte(&off);
+ cie.funcPtrSize = pointerEncodingToSize(pointerEnc);
+ if (cie.funcPtrSize == 0 || !(pointerEnc & dwarf::DW_EH_PE_pcrel))
+ reader.failOn(off, "unexpected pointer encoding 0x" +
+ Twine::utohexstr(pointerEnc));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if (personalityAddrOff != 0) {
+ auto personalityRelocIt =
+ llvm::find_if(isec->relocs, [=](const macho::Reloc &r) {
+ return r.offset == personalityAddrOff;
+ });
+ if (personalityRelocIt == isec->relocs.end())
+ reader.failOn(off, "Failed to locate relocation for personality symbol");
+ cie.personalitySymbol = personalityRelocIt->referent.get<macho::Symbol *>();
+ }
+ return cie;
+}
+
+// EH frame target addresses may be encoded as pcrel offsets. However, instead
+// of using an actual pcrel reloc, ld64 emits subtractor relocations instead.
+// This function recovers the target address from the subtractors, essentially
+// performing the inverse operation of EhRelocator.
+//
+// Concretely, we expect our relocations to write the value of `PC -
+// target_addr` to `PC`. `PC` itself is denoted by a minuend relocation that
+// points to a symbol plus an addend.
+//
+// It is important that the minuend relocation point to a symbol within the
+// same section as the fixup value, since sections may get moved around.
+//
+// For example, for arm64, llvm-mc emits relocations for the target function
+// address like so:
+//
+// ltmp:
+// <CIE start>
+// ...
+// <CIE end>
+// ... multiple FDEs ...
+// <FDE start>
+// <target function address - (ltmp + pcrel offset)>
+// ...
+//
+// If any of the FDEs in `multiple FDEs` get dead-stripped, then `FDE start`
+// will move to an earlier address, and `ltmp + pcrel offset` will no longer
+// reflect an accurate pcrel value. To avoid this problem, we "canonicalize"
+// our relocation by adding an `EH_Frame` symbol at `FDE start`, and updating
+// the reloc to be `target function address - (EH_Frame + new pcrel offset)`.
+//
+// If `Invert` is set, then we instead expect `target_addr - PC` to be written
+// to `PC`.
+template <bool Invert = false>
+Defined *
+targetSymFromCanonicalSubtractor(const InputSection *isec,
+ std::vector<macho::Reloc>::iterator relocIt) {
+ macho::Reloc &subtrahend = *relocIt;
+ macho::Reloc &minuend = *std::next(relocIt);
+ assert(target->hasAttr(subtrahend.type, RelocAttrBits::SUBTRAHEND));
+ assert(target->hasAttr(minuend.type, RelocAttrBits::UNSIGNED));
+ // Note: pcSym may *not* be exactly at the PC; there's usually a non-zero
+ // addend.
+ auto *pcSym = cast<Defined>(subtrahend.referent.get<macho::Symbol *>());
+ Defined *target =
+ cast_or_null<Defined>(minuend.referent.dyn_cast<macho::Symbol *>());
+ if (!pcSym) {
+ auto *targetIsec =
+ cast<ConcatInputSection>(minuend.referent.get<InputSection *>());
+ target = findSymbolAtOffset(targetIsec, minuend.addend);
+ }
+ if (Invert)
+ std::swap(pcSym, target);
+ if (pcSym->isec == isec) {
+ if (pcSym->value - (Invert ? -1 : 1) * minuend.addend != subtrahend.offset)
+ fatal("invalid FDE relocation in __eh_frame");
+ } else {
+ // Ensure the pcReloc points to a symbol within the current EH frame.
+ // HACK: we should really verify that the original relocation's semantics
+ // are preserved. In particular, we should have
+ // `oldSym->value + oldOffset == newSym + newOffset`. However, we don't
+ // have an easy way to access the offsets from this point in the code; some
+ // refactoring is needed for that.
+ macho::Reloc &pcReloc = Invert ? minuend : subtrahend;
+ pcReloc.referent = isec->symbols[0];
+ assert(isec->symbols[0]->value == 0);
+ minuend.addend = pcReloc.offset * (Invert ? 1LL : -1LL);
+ }
+ return target;
+}
+
+Defined *findSymbolAtAddress(const std::vector<Section *> §ions,
+ uint64_t addr) {
+ Section *sec = findContainingSection(sections, &addr);
+ auto *isec = cast<ConcatInputSection>(findContainingSubsection(*sec, &addr));
+ return findSymbolAtOffset(isec, addr);
+}
+
+// For symbols that don't have compact unwind info, associate them with the more
+// general-purpose (and verbose) DWARF unwind info found in __eh_frame.
+//
+// This requires us to parse the contents of __eh_frame. See EhFrame.h for a
+// description of its format.
+//
+// While parsing, we also look for what MC calls "abs-ified" relocations -- they
+// are relocations which are implicitly encoded as offsets in the section data.
+// We convert them into explicit Reloc structs so that the EH frames can be
+// handled just like a regular ConcatInputSection later in our output phase.
+//
+// We also need to handle the case where our input object file has explicit
+// relocations. This is the case when e.g. it's the output of `ld -r`. We only
+// look for the "abs-ified" relocation if an explicit relocation is absent.
+void ObjFile::registerEhFrames(Section &ehFrameSection) {
+ DenseMap<const InputSection *, CIE> cieMap;
+ for (const Subsection &subsec : ehFrameSection.subsections) {
+ auto *isec = cast<ConcatInputSection>(subsec.isec);
+ uint64_t isecOff = subsec.offset;
+
+ // Subtractor relocs require the subtrahend to be a symbol reloc. Ensure
+ // that all EH frames have an associated symbol so that we can generate
+ // subtractor relocs that reference them.
+ if (isec->symbols.size() == 0)
+ make<Defined>("EH_Frame", isec->getFile(), isec, /*value=*/0,
+ isec->getSize(), /*isWeakDef=*/false, /*isExternal=*/false,
+ /*isPrivateExtern=*/false, /*includeInSymtab=*/false,
+ /*isThumb=*/false, /*isReferencedDynamically=*/false,
+ /*noDeadStrip=*/false);
+ else if (isec->symbols[0]->value != 0)
+ fatal("found symbol at unexpected offset in __eh_frame");
+
+ EhReader reader(this, isec->data, subsec.offset);
+ size_t dataOff = 0; // Offset from the start of the EH frame.
+ reader.skipValidLength(&dataOff); // readLength() already validated this.
+ // cieOffOff is the offset from the start of the EH frame to the cieOff
+ // value, which is itself an offset from the current PC to a CIE.
+ const size_t cieOffOff = dataOff;
+
+ EhRelocator ehRelocator(isec);
+ auto cieOffRelocIt = llvm::find_if(
+ isec->relocs, [=](const Reloc &r) { return r.offset == cieOffOff; });
+ InputSection *cieIsec = nullptr;
+ if (cieOffRelocIt != isec->relocs.end()) {
+ // We already have an explicit relocation for the CIE offset.
+ cieIsec =
+ targetSymFromCanonicalSubtractor</*Invert=*/true>(isec, cieOffRelocIt)
+ ->isec;
+ dataOff += sizeof(uint32_t);
+ } else {
+ // If we haven't found a relocation, then the CIE offset is most likely
+ // embedded in the section data (AKA an "abs-ified" reloc.). Parse that
+ // and generate a Reloc struct.
+ uint32_t cieMinuend = reader.readU32(&dataOff);
+ if (cieMinuend == 0) {
+ cieIsec = isec;
+ } else {
+ uint32_t cieOff = isecOff + dataOff - cieMinuend;
+ cieIsec = findContainingSubsection(ehFrameSection, &cieOff);
+ if (cieIsec == nullptr)
+ fatal("failed to find CIE");
+ }
+ if (cieIsec != isec)
+ ehRelocator.makeNegativePcRel(cieOffOff, cieIsec->symbols[0],
+ /*length=*/2);
+ }
+ if (cieIsec == isec) {
+ cieMap[cieIsec] = parseCIE(isec, reader, dataOff);
+ continue;
+ }
+
+ assert(cieMap.count(cieIsec));
+ const CIE &cie = cieMap[cieIsec];
+ // Offset of the function address within the EH frame.
+ const size_t funcAddrOff = dataOff;
+ uint64_t funcAddr = reader.readPointer(&dataOff, cie.funcPtrSize) +
+ ehFrameSection.addr + isecOff + funcAddrOff;
+ uint32_t funcLength = reader.readPointer(&dataOff, cie.funcPtrSize);
+ size_t lsdaAddrOff = 0; // Offset of the LSDA address within the EH frame.
+ std::optional<uint64_t> lsdaAddrOpt;
+ if (cie.fdesHaveAug) {
+ reader.skipLeb128(&dataOff);
+ lsdaAddrOff = dataOff;
+ if (cie.lsdaPtrSize != 0) {
+ uint64_t lsdaOff = reader.readPointer(&dataOff, cie.lsdaPtrSize);
+ if (lsdaOff != 0) // FIXME possible to test this?
+ lsdaAddrOpt = ehFrameSection.addr + isecOff + lsdaAddrOff + lsdaOff;
+ }
+ }
+
+ auto funcAddrRelocIt = isec->relocs.end();
+ auto lsdaAddrRelocIt = isec->relocs.end();
+ for (auto it = isec->relocs.begin(); it != isec->relocs.end(); ++it) {
+ if (it->offset == funcAddrOff)
+ funcAddrRelocIt = it++; // Found subtrahend; skip over minuend reloc
+ else if (lsdaAddrOpt && it->offset == lsdaAddrOff)
+ lsdaAddrRelocIt = it++; // Found subtrahend; skip over minuend reloc
+ }
+
+ Defined *funcSym;
+ if (funcAddrRelocIt != isec->relocs.end()) {
+ funcSym = targetSymFromCanonicalSubtractor(isec, funcAddrRelocIt);
+ // Canonicalize the symbol. If there are multiple symbols at the same
+ // address, we want both `registerEhFrame` and `registerCompactUnwind`
+ // to register the unwind entry under same symbol.
+ // This is not particularly efficient, but we should run into this case
+ // infrequently (only when handling the output of `ld -r`).
+ if (funcSym->isec)
+ funcSym = findSymbolAtOffset(cast<ConcatInputSection>(funcSym->isec),
+ funcSym->value);
+ } else {
+ funcSym = findSymbolAtAddress(sections, funcAddr);
+ ehRelocator.makePcRel(funcAddrOff, funcSym, target->p2WordSize);
+ }
+ // The symbol has been coalesced, or already has a compact unwind entry.
+ if (!funcSym || funcSym->getFile() != this || funcSym->unwindEntry) {
+ // We must prune unused FDEs for correctness, so we cannot rely on
+ // -dead_strip being enabled.
+ isec->live = false;
+ continue;
+ }
+
+ InputSection *lsdaIsec = nullptr;
+ if (lsdaAddrRelocIt != isec->relocs.end()) {
+ lsdaIsec = targetSymFromCanonicalSubtractor(isec, lsdaAddrRelocIt)->isec;
+ } else if (lsdaAddrOpt) {
+ uint64_t lsdaAddr = *lsdaAddrOpt;
+ Section *sec = findContainingSection(sections, &lsdaAddr);
+ lsdaIsec =
+ cast<ConcatInputSection>(findContainingSubsection(*sec, &lsdaAddr));
+ ehRelocator.makePcRel(lsdaAddrOff, lsdaIsec, target->p2WordSize);
+ }
+
+ fdes[isec] = {funcLength, cie.personalitySymbol, lsdaIsec};
+ funcSym->unwindEntry = isec;
+ ehRelocator.commit();
+ }
+
+ // __eh_frame is marked as S_ATTR_LIVE_SUPPORT in input files, because FDEs
+ // are normally required to be kept alive if they reference a live symbol.
+ // However, we've explicitly created a dependency from a symbol to its FDE, so
+ // dead-stripping will just work as usual, and S_ATTR_LIVE_SUPPORT will only
+ // serve to incorrectly prevent us from dead-stripping duplicate FDEs for a
+ // live symbol (e.g. if there were multiple weak copies). Remove this flag to
+ // let dead-stripping proceed correctly.
+ ehFrameSection.flags &= ~S_ATTR_LIVE_SUPPORT;
+}
+
+std::string ObjFile::sourceFile() const {
+ SmallString<261> dir(compileUnit->getCompilationDir());
+ StringRef sep = sys::path::get_separator();
+ // We don't use `path::append` here because we want an empty `dir` to result
+ // in an absolute path. `append` would give us a relative path for that case.
+ if (!dir.endswith(sep))
+ dir += sep;
+ return (dir + compileUnit->getUnitDIE().getShortName()).str();
+}
+
+lld::DWARFCache *ObjFile::getDwarf() {
+ llvm::call_once(initDwarf, [this]() {
+ auto dwObj = DwarfObject::create(this);
+ if (!dwObj)
+ return;
+ dwarfCache = std::make_unique<DWARFCache>(std::make_unique<DWARFContext>(
+ std::move(dwObj), "",
+ [&](Error err) { warn(getName() + ": " + toString(std::move(err))); },
+ [&](Error warning) {
+ warn(getName() + ": " + toString(std::move(warning)));
+ }));
+ });
+
+ return dwarfCache.get();
+}
// The path can point to either a dylib or a .tbd file.
static DylibFile *loadDylib(StringRef path, DylibFile *umbrella) {
- Optional<MemoryBufferRef> mbref = readFile(path);
+ std::optional<MemoryBufferRef> mbref = readFile(path);
if (!mbref) {
error("could not read dylib file at " + path);
return nullptr;
for (StringRef dir : config->frameworkSearchPaths) {
SmallString<128> candidate = dir;
path::append(candidate, frameworkName);
- if (Optional<std::string> dylibPath = resolveDylibPath(candidate))
+ if (std::optional<StringRef> dylibPath =
+ resolveDylibPath(candidate.str()))
return loadDylib(*dylibPath, umbrella);
}
- } else if (Optional<StringRef> dylibPath = findPathCombination(
+ } else if (std::optional<StringRef> dylibPath = findPathCombination(
stem, config->librarySearchPaths, {".tbd", ".dylib"}))
return loadDylib(*dylibPath, umbrella);
}
// 2. As absolute path.
if (path::is_absolute(path, path::Style::posix))
for (StringRef root : config->systemLibraryRoots)
- if (Optional<std::string> dylibPath =
+ if (std::optional<StringRef> dylibPath =
resolveDylibPath((root + path).str()))
return loadDylib(*dylibPath, umbrella);
path::remove_filename(newPath);
}
path::append(newPath, rpath, path.drop_front(strlen("@rpath/")));
- if (Optional<std::string> dylibPath = resolveDylibPath(newPath))
+ if (std::optional<StringRef> dylibPath = resolveDylibPath(newPath.str()))
return loadDylib(*dylibPath, umbrella);
}
}
make_pointee_range(currentTopLevelTapi->documents())) {
assert(child.documents().empty());
if (path == child.getInstallName()) {
- auto file = make<DylibFile>(child, umbrella);
+ auto file = make<DylibFile>(child, umbrella, /*isBundleLoader=*/false,
+ /*explicitlyLinked=*/false);
file->parseReexports(child);
return file;
}
}
}
- if (Optional<std::string> dylibPath = resolveDylibPath(path))
+ if (std::optional<StringRef> dylibPath = resolveDylibPath(path))
return loadDylib(*dylibPath, umbrella);
return nullptr;
return false;
}
-static void loadReexport(StringRef path, DylibFile *umbrella,
+void DylibFile::loadReexport(StringRef path, DylibFile *umbrella,
const InterfaceFile *currentTopLevelTapi) {
DylibFile *reexport = findDylib(path, umbrella, currentTopLevelTapi);
if (!reexport)
- error("unable to locate re-export with install name " + path);
+ error(toString(this) + ": unable to locate re-export with install name " +
+ path);
}
DylibFile::DylibFile(MemoryBufferRef mb, DylibFile *umbrella,
- bool isBundleLoader)
+ bool isBundleLoader, bool explicitlyLinked)
: InputFile(DylibKind, mb), refState(RefState::Unreferenced),
- isBundleLoader(isBundleLoader) {
+ explicitlyLinked(explicitlyLinked), isBundleLoader(isBundleLoader) {
assert(!isBundleLoader || !umbrella);
if (umbrella == nullptr)
umbrella = this;
this->umbrella = umbrella;
- auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
auto *hdr = reinterpret_cast<const mach_header *>(mb.getBufferStart());
// Initialize installName.
} else if (!isBundleLoader) {
// macho_executable and macho_bundle don't have LC_ID_DYLIB,
// so it's OK.
- error("dylib " + toString(this) + " missing LC_ID_DYLIB load command");
+ error(toString(this) + ": dylib missing LC_ID_DYLIB load command");
return;
}
// Initialize symbols.
exportingFile = isImplicitlyLinked(installName) ? this : this->umbrella;
- if (const load_command *cmd = findCommand(hdr, LC_DYLD_INFO_ONLY)) {
- auto *c = reinterpret_cast<const dyld_info_command *>(cmd);
- parseTrie(buf + c->export_off, c->export_size,
- [&](const Twine &name, uint64_t flags) {
- StringRef savedName = saver.save(name);
- if (handleLDSymbol(savedName))
- return;
- bool isWeakDef = flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
- bool isTlv = flags & EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL;
- symbols.push_back(symtab->addDylib(savedName, exportingFile,
- isWeakDef, isTlv));
- });
+
+ const auto *dyldInfo = findCommand<dyld_info_command>(hdr, LC_DYLD_INFO_ONLY);
+ const auto *exportsTrie =
+ findCommand<linkedit_data_command>(hdr, LC_DYLD_EXPORTS_TRIE);
+ if (dyldInfo && exportsTrie) {
+ // It's unclear what should happen in this case. Maybe we should only error
+ // out if the two load commands refer to different data?
+ error(toString(this) +
+ ": dylib has both LC_DYLD_INFO_ONLY and LC_DYLD_EXPORTS_TRIE");
+ return;
+ } else if (dyldInfo) {
+ parseExportedSymbols(dyldInfo->export_off, dyldInfo->export_size);
+ } else if (exportsTrie) {
+ parseExportedSymbols(exportsTrie->dataoff, exportsTrie->datasize);
} else {
- error("LC_DYLD_INFO_ONLY not found in " + toString(this));
+ error("No LC_DYLD_INFO_ONLY or LC_DYLD_EXPORTS_TRIE found in " +
+ toString(this));
return;
}
}
+void DylibFile::parseExportedSymbols(uint32_t offset, uint32_t size) {
+ struct TrieEntry {
+ StringRef name;
+ uint64_t flags;
+ };
+
+ auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
+ std::vector<TrieEntry> entries;
+ // Find all the $ld$* symbols to process first.
+ parseTrie(buf + offset, size, [&](const Twine &name, uint64_t flags) {
+ StringRef savedName = saver().save(name);
+ if (handleLDSymbol(savedName))
+ return;
+ entries.push_back({savedName, flags});
+ });
+
+ // Process the "normal" symbols.
+ for (TrieEntry &entry : entries) {
+ if (exportingFile->hiddenSymbols.contains(CachedHashStringRef(entry.name)))
+ continue;
+
+ bool isWeakDef = entry.flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
+ bool isTlv = entry.flags & EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL;
+
+ symbols.push_back(
+ symtab->addDylib(entry.name, exportingFile, isWeakDef, isTlv));
+ }
+}
+
void DylibFile::parseLoadCommands(MemoryBufferRef mb) {
auto *hdr = reinterpret_cast<const mach_header *>(mb.getBufferStart());
const uint8_t *p = reinterpret_cast<const uint8_t *>(mb.getBufferStart()) +
}
}
-// Some versions of XCode ship with .tbd files that don't have the right
+// Some versions of Xcode ship with .tbd files that don't have the right
// platform settings.
-static constexpr std::array<StringRef, 3> skipPlatformChecks{
+constexpr std::array<StringRef, 3> skipPlatformChecks{
"/usr/lib/system/libsystem_kernel.dylib",
"/usr/lib/system/libsystem_platform.dylib",
"/usr/lib/system/libsystem_pthread.dylib"};
+static bool skipPlatformCheckForCatalyst(const InterfaceFile &interface,
+ bool explicitlyLinked) {
+ // Catalyst outputs can link against implicitly linked macOS-only libraries.
+ if (config->platform() != PLATFORM_MACCATALYST || explicitlyLinked)
+ return false;
+ return is_contained(interface.targets(),
+ MachO::Target(config->arch(), PLATFORM_MACOS));
+}
+
+static bool isArchABICompatible(ArchitectureSet archSet,
+ Architecture targetArch) {
+ uint32_t cpuType;
+ uint32_t targetCpuType;
+ std::tie(targetCpuType, std::ignore) = getCPUTypeFromArchitecture(targetArch);
+
+ return llvm::any_of(archSet, [&](const auto &p) {
+ std::tie(cpuType, std::ignore) = getCPUTypeFromArchitecture(p);
+ return cpuType == targetCpuType;
+ });
+}
+
+static bool isTargetPlatformArchCompatible(
+ InterfaceFile::const_target_range interfaceTargets, Target target) {
+ if (is_contained(interfaceTargets, target))
+ return true;
+
+ if (config->forceExactCpuSubtypeMatch)
+ return false;
+
+ ArchitectureSet archSet;
+ for (const auto &p : interfaceTargets)
+ if (p.Platform == target.Platform)
+ archSet.set(p.Arch);
+ if (archSet.empty())
+ return false;
+
+ return isArchABICompatible(archSet, target.Arch);
+}
+
DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella,
- bool isBundleLoader)
+ bool isBundleLoader, bool explicitlyLinked)
: InputFile(DylibKind, interface), refState(RefState::Unreferenced),
- isBundleLoader(isBundleLoader) {
+ explicitlyLinked(explicitlyLinked), isBundleLoader(isBundleLoader) {
// FIXME: Add test for the missing TBD code path.
if (umbrella == nullptr)
umbrella = this;
this->umbrella = umbrella;
- installName = saver.save(interface.getInstallName());
+ installName = saver().save(interface.getInstallName());
compatibilityVersion = interface.getCompatibilityVersion().rawValue();
currentVersion = interface.getCurrentVersion().rawValue();
inputFiles.insert(this);
if (!is_contained(skipPlatformChecks, installName) &&
- !is_contained(interface.targets(), config->platformInfo.target)) {
+ !isTargetPlatformArchCompatible(interface.targets(),
+ config->platformInfo.target) &&
+ !skipPlatformCheckForCatalyst(interface, explicitlyLinked)) {
error(toString(this) + " is incompatible with " +
std::string(config->platformInfo.target));
return;
checkAppExtensionSafety(interface.isApplicationExtensionSafe());
exportingFile = isImplicitlyLinked(installName) ? this : umbrella;
- auto addSymbol = [&](const Twine &name) -> void {
- symbols.push_back(symtab->addDylib(saver.save(name), exportingFile,
- /*isWeakDef=*/false,
- /*isTlv=*/false));
+ auto addSymbol = [&](const llvm::MachO::Symbol &symbol,
+ const Twine &name) -> void {
+ StringRef savedName = saver().save(name);
+ if (exportingFile->hiddenSymbols.contains(CachedHashStringRef(savedName)))
+ return;
+
+ symbols.push_back(symtab->addDylib(savedName, exportingFile,
+ symbol.isWeakDefined(),
+ symbol.isThreadLocalValue()));
};
- // TODO(compnerd) filter out symbols based on the target platform
- // TODO: handle weak defs, thread locals
+
+ std::vector<const llvm::MachO::Symbol *> normalSymbols;
+ normalSymbols.reserve(interface.symbolsCount());
for (const auto *symbol : interface.symbols()) {
- if (!symbol->getArchitectures().has(config->arch()))
+ if (!isArchABICompatible(symbol->getArchitectures(), config->arch()))
continue;
-
if (handleLDSymbol(symbol->getName()))
continue;
switch (symbol->getKind()) {
case SymbolKind::GlobalSymbol:
- addSymbol(symbol->getName());
+ case SymbolKind::ObjectiveCClass:
+ case SymbolKind::ObjectiveCClassEHType:
+ case SymbolKind::ObjectiveCInstanceVariable:
+ normalSymbols.push_back(symbol);
+ }
+ }
+
+ // TODO(compnerd) filter out symbols based on the target platform
+ for (const auto *symbol : normalSymbols) {
+ switch (symbol->getKind()) {
+ case SymbolKind::GlobalSymbol:
+ addSymbol(*symbol, symbol->getName());
break;
case SymbolKind::ObjectiveCClass:
// XXX ld64 only creates these symbols when -ObjC is passed in. We may
// want to emulate that.
- addSymbol(objc::klass + symbol->getName());
- addSymbol(objc::metaclass + symbol->getName());
+ addSymbol(*symbol, objc::klass + symbol->getName());
+ addSymbol(*symbol, objc::metaclass + symbol->getName());
break;
case SymbolKind::ObjectiveCClassEHType:
- addSymbol(objc::ehtype + symbol->getName());
+ addSymbol(*symbol, objc::ehtype + symbol->getName());
break;
case SymbolKind::ObjectiveCInstanceVariable:
- addSymbol(objc::ivar + symbol->getName());
+ addSymbol(*symbol, objc::ivar + symbol->getName());
break;
}
}
}
+DylibFile::DylibFile(DylibFile *umbrella)
+ : InputFile(DylibKind, MemoryBufferRef{}), refState(RefState::Unreferenced),
+ explicitlyLinked(false), isBundleLoader(false) {
+ if (umbrella == nullptr)
+ umbrella = this;
+ this->umbrella = umbrella;
+}
+
void DylibFile::parseReexports(const InterfaceFile &interface) {
const InterfaceFile *topLevel =
interface.getParent() == nullptr ? &interface : interface.getParent();
- for (InterfaceFileRef intfRef : interface.reexportedLibraries()) {
+ for (const InterfaceFileRef &intfRef : interface.reexportedLibraries()) {
InterfaceFile::const_target_range targets = intfRef.targets();
if (is_contained(skipPlatformChecks, intfRef.getInstallName()) ||
- is_contained(targets, config->platformInfo.target))
+ isTargetPlatformArchCompatible(targets, config->platformInfo.target))
loadReexport(intfRef.getInstallName(), exportingFile, topLevel);
}
}
+bool DylibFile::isExplicitlyLinked() const {
+ if (!explicitlyLinked)
+ return false;
+
+ // If this dylib was explicitly linked, but at least one of the symbols
+ // of the synthetic dylibs it created via $ld$previous symbols is
+ // referenced, then that synthetic dylib fulfils the explicit linkedness
+ // and we can deadstrip this dylib if it's unreferenced.
+ for (const auto *dylib : extraDylibs)
+ if (dylib->isReferenced())
+ return false;
+
+ return true;
+}
+
+DylibFile *DylibFile::getSyntheticDylib(StringRef installName,
+ uint32_t currentVersion,
+ uint32_t compatVersion) {
+ for (DylibFile *dylib : extraDylibs)
+ if (dylib->installName == installName) {
+ // FIXME: Check what to do if different $ld$previous symbols
+ // request the same dylib, but with different versions.
+ return dylib;
+ }
+
+ auto *dylib = make<DylibFile>(umbrella == this ? nullptr : umbrella);
+ dylib->installName = saver().save(installName);
+ dylib->currentVersion = currentVersion;
+ dylib->compatibilityVersion = compatVersion;
+ extraDylibs.push_back(dylib);
+ return dylib;
+}
+
// $ld$ symbols modify the properties/behavior of the library (e.g. its install
// name, compatibility version or hide/add symbols) for specific target
// versions.
handleLDPreviousSymbol(name, originalName);
else if (action == "install_name")
handleLDInstallNameSymbol(name, originalName);
+ else if (action == "hide")
+ handleLDHideSymbol(name, originalName);
return true;
}
std::tie(platformStr, name) = name.split('$');
std::tie(startVersion, name) = name.split('$');
std::tie(endVersion, name) = name.split('$');
- std::tie(symbolName, rest) = name.split('$');
- // TODO: ld64 contains some logic for non-empty symbolName as well.
- if (!symbolName.empty())
- return;
+ std::tie(symbolName, rest) = name.rsplit('$');
+
+ // FIXME: Does this do the right thing for zippered files?
unsigned platform;
if (platformStr.getAsInteger(10, platform) ||
platform != static_cast<unsigned>(config->platform()))
VersionTuple start;
if (start.tryParse(startVersion)) {
- warn("failed to parse start version, symbol '" + originalName +
- "' ignored");
+ warn(toString(this) + ": failed to parse start version, symbol '" +
+ originalName + "' ignored");
return;
}
VersionTuple end;
if (end.tryParse(endVersion)) {
- warn("failed to parse end version, symbol '" + originalName + "' ignored");
+ warn(toString(this) + ": failed to parse end version, symbol '" +
+ originalName + "' ignored");
return;
}
if (config->platformInfo.minimum < start ||
config->platformInfo.minimum >= end)
return;
- this->installName = saver.save(installName);
-
+ // Initialized to compatibilityVersion for the symbolName branch below.
+ uint32_t newCompatibilityVersion = compatibilityVersion;
+ uint32_t newCurrentVersionForSymbol = currentVersion;
if (!compatVersion.empty()) {
VersionTuple cVersion;
if (cVersion.tryParse(compatVersion)) {
- warn("failed to parse compatibility version, symbol '" + originalName +
+ warn(toString(this) +
+ ": failed to parse compatibility version, symbol '" + originalName +
"' ignored");
return;
}
- compatibilityVersion = encodeVersion(cVersion);
+ newCompatibilityVersion = encodeVersion(cVersion);
+ newCurrentVersionForSymbol = newCompatibilityVersion;
+ }
+
+ if (!symbolName.empty()) {
+ // A $ld$previous$ symbol with symbol name adds a symbol with that name to
+ // a dylib with given name and version.
+ auto *dylib = getSyntheticDylib(installName, newCurrentVersionForSymbol,
+ newCompatibilityVersion);
+
+ // The tbd file usually contains the $ld$previous symbol for an old version,
+ // and then the symbol itself later, for newer deployment targets, like so:
+ // symbols: [
+ // '$ld$previous$/Another$$1$3.0$14.0$_zzz$',
+ // _zzz,
+ // ]
+ // Since the symbols are sorted, adding them to the symtab in the given
+ // order means the $ld$previous version of _zzz will prevail, as desired.
+ dylib->symbols.push_back(symtab->addDylib(
+ saver().save(symbolName), dylib, /*isWeakDef=*/false, /*isTlv=*/false));
+ return;
}
+
+ // A $ld$previous$ symbol without symbol name modifies the dylib it's in.
+ this->installName = saver().save(installName);
+ this->compatibilityVersion = newCompatibilityVersion;
}
void DylibFile::handleLDInstallNameSymbol(StringRef name,
std::tie(condition, installName) = name.split('$');
VersionTuple version;
if (!condition.consume_front("os") || version.tryParse(condition))
- warn("failed to parse os version, symbol '" + originalName + "' ignored");
+ warn(toString(this) + ": failed to parse os version, symbol '" +
+ originalName + "' ignored");
else if (version == config->platformInfo.minimum)
- this->installName = saver.save(installName);
+ this->installName = saver().save(installName);
+}
+
+void DylibFile::handleLDHideSymbol(StringRef name, StringRef originalName) {
+ StringRef symbolName;
+ bool shouldHide = true;
+ if (name.startswith("os")) {
+ // If it's hidden based on versions.
+ name = name.drop_front(2);
+ StringRef minVersion;
+ std::tie(minVersion, symbolName) = name.split('$');
+ VersionTuple versionTup;
+ if (versionTup.tryParse(minVersion)) {
+ warn(toString(this) + ": failed to parse hidden version, symbol `" + originalName +
+ "` ignored.");
+ return;
+ }
+ shouldHide = versionTup == config->platformInfo.minimum;
+ } else {
+ symbolName = name;
+ }
+
+ if (shouldHide)
+ exportingFile->hiddenSymbols.insert(CachedHashStringRef(symbolName));
}
void DylibFile::checkAppExtensionSafety(bool dylibIsAppExtensionSafe) const {
warn("using '-application_extension' with unsafe dylib: " + toString(this));
}
-ArchiveFile::ArchiveFile(std::unique_ptr<object::Archive> &&f)
- : InputFile(ArchiveKind, f->getMemoryBufferRef()), file(std::move(f)) {
+ArchiveFile::ArchiveFile(std::unique_ptr<object::Archive> &&f, bool forceHidden)
+ : InputFile(ArchiveKind, f->getMemoryBufferRef()), file(std::move(f)),
+ forceHidden(forceHidden) {}
+
+void ArchiveFile::addLazySymbols() {
for (const object::Archive::Symbol &sym : file->symbols())
- symtab->addLazy(sym.getName(), this, sym);
+ symtab->addLazyArchive(sym.getName(), this, sym);
}
-void ArchiveFile::fetch(const object::Archive::Symbol &sym) {
- object::Archive::Child c =
- CHECK(sym.getMember(), toString(this) +
- ": could not get the member for symbol " +
- toMachOString(sym));
+static Expected<InputFile *>
+loadArchiveMember(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName,
+ uint64_t offsetInArchive, bool forceHidden) {
+ if (config->zeroModTime)
+ modTime = 0;
+
+ switch (identify_magic(mb.getBuffer())) {
+ case file_magic::macho_object:
+ return make<ObjFile>(mb, modTime, archiveName, /*lazy=*/false, forceHidden);
+ case file_magic::bitcode:
+ return make<BitcodeFile>(mb, archiveName, offsetInArchive, /*lazy=*/false,
+ forceHidden);
+ default:
+ return createStringError(inconvertibleErrorCode(),
+ mb.getBufferIdentifier() +
+ " has unhandled file type");
+ }
+}
+Error ArchiveFile::fetch(const object::Archive::Child &c, StringRef reason) {
if (!seen.insert(c.getChildOffset()).second)
- return;
+ return Error::success();
- MemoryBufferRef mb =
- CHECK(c.getMemoryBufferRef(),
- toString(this) +
- ": could not get the buffer for the member defining symbol " +
- toMachOString(sym));
+ Expected<MemoryBufferRef> mb = c.getMemoryBufferRef();
+ if (!mb)
+ return mb.takeError();
+ // Thin archives refer to .o files, so --reproduce needs the .o files too.
if (tar && c.getParent()->isThin())
- tar->append(relativeToRoot(CHECK(c.getFullName(), this)), mb.getBuffer());
+ tar->append(relativeToRoot(CHECK(c.getFullName(), this)), mb->getBuffer());
+
+ Expected<TimePoint<std::chrono::seconds>> modTime = c.getLastModified();
+ if (!modTime)
+ return modTime.takeError();
- uint32_t modTime = toTimeT(
- CHECK(c.getLastModified(), toString(this) +
- ": could not get the modification time "
- "for the member defining symbol " +
- toMachOString(sym)));
+ Expected<InputFile *> file = loadArchiveMember(
+ *mb, toTimeT(*modTime), getName(), c.getChildOffset(), forceHidden);
+
+ if (!file)
+ return file.takeError();
+
+ inputFiles.insert(*file);
+ printArchiveMemberLoad(reason, *file);
+ return Error::success();
+}
+
+void ArchiveFile::fetch(const object::Archive::Symbol &sym) {
+ object::Archive::Child c =
+ CHECK(sym.getMember(), toString(this) +
+ ": could not get the member defining symbol " +
+ toMachOString(sym));
// `sym` is owned by a LazySym, which will be replace<>()d by make<ObjFile>
// and become invalid after that call. Copy it to the stack so we can refer
// to it later.
const object::Archive::Symbol symCopy = sym;
- if (Optional<InputFile *> file = loadArchiveMember(
- mb, modTime, getName(), /*objCOnly=*/false, c.getChildOffset())) {
- inputFiles.insert(*file);
- // ld64 doesn't demangle sym here even with -demangle.
- // Match that: intentionally don't call toMachOString().
- printArchiveMemberLoad(symCopy.getName(), *file);
- }
+ // ld64 doesn't demangle sym here even with -demangle.
+ // Match that: intentionally don't call toMachOString().
+ if (Error e = fetch(c, symCopy.getName()))
+ error(toString(this) + ": could not get the member defining symbol " +
+ toMachOString(symCopy) + ": " + toString(std::move(e)));
}
static macho::Symbol *createBitcodeSymbol(const lto::InputFile::Symbol &objSym,
BitcodeFile &file) {
- StringRef name = saver.save(objSym.getName());
+ StringRef name = saver().save(objSym.getName());
- // TODO: support weak references
if (objSym.isUndefined())
- return symtab->addUndefined(name, &file, /*isWeakRef=*/false);
-
- assert(!objSym.isCommon() && "TODO: support common symbols in LTO");
+ return symtab->addUndefined(name, &file, /*isWeakRef=*/objSym.isWeak());
// TODO: Write a test demonstrating why computing isPrivateExtern before
// LTO compilation is important.
case GlobalValue::DefaultVisibility:
break;
}
+ isPrivateExtern = isPrivateExtern || objSym.canBeOmittedFromSymbolTable() ||
+ file.forceHidden;
+
+ if (objSym.isCommon())
+ return symtab->addCommon(name, &file, objSym.getCommonSize(),
+ objSym.getCommonAlignment(), isPrivateExtern);
return symtab->addDefined(name, &file, /*isec=*/nullptr, /*value=*/0,
/*size=*/0, objSym.isWeak(), isPrivateExtern,
/*isThumb=*/false,
/*isReferencedDynamically=*/false,
- /*noDeadStrip=*/false);
+ /*noDeadStrip=*/false,
+ /*isWeakDefCanBeHidden=*/false);
}
BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
- uint64_t offsetInArchive)
- : InputFile(BitcodeKind, mb) {
+ uint64_t offsetInArchive, bool lazy, bool forceHidden)
+ : InputFile(BitcodeKind, mb, lazy), forceHidden(forceHidden) {
+ this->archiveName = std::string(archiveName);
std::string path = mb.getBufferIdentifier().str();
+ if (config->thinLTOIndexOnly)
+ path = replaceThinLTOSuffix(mb.getBufferIdentifier());
+
// ThinLTO assumes that all MemoryBufferRefs given to it have a unique
// name. If two members with the same name are provided, this causes a
// collision and ThinLTO can't proceed.
// So, we append the archive name to disambiguate two members with the same
// name from multiple different archives, and offset within the archive to
// disambiguate two members of the same name from a single archive.
- MemoryBufferRef mbref(
- mb.getBuffer(),
- saver.save(archiveName.empty() ? path
- : archiveName + sys::path::filename(path) +
- utostr(offsetInArchive)));
+ MemoryBufferRef mbref(mb.getBuffer(),
+ saver().save(archiveName.empty()
+ ? path
+ : archiveName +
+ sys::path::filename(path) +
+ utostr(offsetInArchive)));
obj = check(lto::InputFile::create(mbref));
+ if (lazy)
+ parseLazy();
+ else
+ parse();
+}
+void BitcodeFile::parse() {
// Convert LTO Symbols to LLD Symbols in order to perform resolution. The
// "winning" symbol will then be marked as Prevailing at LTO compilation
// time.
+ symbols.clear();
for (const lto::InputFile::Symbol &objSym : obj->symbols())
symbols.push_back(createBitcodeSymbol(objSym, *this));
}
+void BitcodeFile::parseLazy() {
+ symbols.resize(obj->symbols().size());
+ for (const auto &[i, objSym] : llvm::enumerate(obj->symbols())) {
+ if (!objSym.isUndefined()) {
+ symbols[i] = symtab->addLazyObject(saver().save(objSym.getName()), *this);
+ if (!lazy)
+ break;
+ }
+ }
+}
+
+std::string macho::replaceThinLTOSuffix(StringRef path) {
+ auto [suffix, repl] = config->thinLTOObjectSuffixReplace;
+ if (path.consume_back(suffix))
+ return (path + repl).str();
+ return std::string(path);
+}
+
+void macho::extract(InputFile &file, StringRef reason) {
+ if (!file.lazy)
+ return;
+ file.lazy = false;
+
+ printArchiveMemberLoad(reason, &file);
+ if (auto *bitcode = dyn_cast<BitcodeFile>(&file)) {
+ bitcode->parse();
+ } else {
+ auto &f = cast<ObjFile>(file);
+ if (target->wordSize == 8)
+ f.parse<LP64>();
+ else
+ f.parse<ILP32>();
+ }
+}
+
template void ObjFile::parse<LP64>();
#include "MachOStructs.h"
#include "Target.h"
+#include "lld/Common/DWARF.h"
#include "lld/Common/LLVM.h"
#include "lld/Common/Memory.h"
+#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/DebugInfo/DWARF/DWARFUnit.h"
#include "llvm/Object/Archive.h"
#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Threading.h"
#include "llvm/TextAPI/TextAPIReader.h"
#include <vector>
struct PlatformInfo;
class ConcatInputSection;
class Symbol;
+class Defined;
+class AliasSymbol;
struct Reloc;
enum class RefState : uint8_t;
// If .subsections_via_symbols is set, each InputSection will be split along
// symbol boundaries. The field offset represents the offset of the subsection
// from the start of the original pre-split InputSection.
-struct SubsectionEntry {
- uint64_t offset;
- InputSection *isec;
+struct Subsection {
+ uint64_t offset = 0;
+ InputSection *isec = nullptr;
+};
+
+using Subsections = std::vector<Subsection>;
+class InputFile;
+
+class Section {
+public:
+ InputFile *file;
+ StringRef segname;
+ StringRef name;
+ uint32_t flags;
+ uint64_t addr;
+ Subsections subsections;
+
+ Section(InputFile *file, StringRef segname, StringRef name, uint32_t flags,
+ uint64_t addr)
+ : file(file), segname(segname), name(name), flags(flags), addr(addr) {}
+ // Ensure pointers to Sections are never invalidated.
+ Section(const Section &) = delete;
+ Section &operator=(const Section &) = delete;
+ Section(Section &&) = delete;
+ Section &operator=(Section &&) = delete;
+
+private:
+ // Whether we have already split this section into individual subsections.
+ // For sections that cannot be split (e.g. literal sections), this is always
+ // false.
+ bool doneSplitting = false;
+ friend class ObjFile;
+};
+
+// Represents a call graph profile edge.
+struct CallGraphEntry {
+ // The index of the caller in the symbol table.
+ uint32_t fromIndex;
+ // The index of the callee in the symbol table.
+ uint32_t toIndex;
+ // Number of calls from callee to caller in the profile.
+ uint64_t count;
+
+ CallGraphEntry(uint32_t fromIndex, uint32_t toIndex, uint64_t count)
+ : fromIndex(fromIndex), toIndex(toIndex), count(count) {}
};
-using SubsectionMap = std::vector<SubsectionEntry>;
class InputFile {
public:
virtual ~InputFile() = default;
Kind kind() const { return fileKind; }
StringRef getName() const { return name; }
+ static void resetIdCount() { idCount = 0; }
MemoryBufferRef mb;
std::vector<Symbol *> symbols;
- std::vector<SubsectionMap> subsections;
- // Provides an easy way to sort InputFiles deterministically.
- const int id;
+ std::vector<Section *> sections;
+ ArrayRef<uint8_t> objCImageInfo;
// If not empty, this stores the name of the archive containing this file.
// We use this string for creating error messages.
std::string archiveName;
+ // Provides an easy way to sort InputFiles deterministically.
+ const int id;
+
+ // True if this is a lazy ObjFile or BitcodeFile.
+ bool lazy = false;
+
protected:
- InputFile(Kind kind, MemoryBufferRef mb)
- : mb(mb), id(idCount++), fileKind(kind), name(mb.getBufferIdentifier()) {}
+ InputFile(Kind kind, MemoryBufferRef mb, bool lazy = false)
+ : mb(mb), id(idCount++), lazy(lazy), fileKind(kind),
+ name(mb.getBufferIdentifier()) {}
InputFile(Kind, const llvm::MachO::InterfaceFile &);
static int idCount;
};
+struct FDE {
+ uint32_t funcLength;
+ Symbol *personality;
+ InputSection *lsda;
+};
+
// .o file
class ObjFile final : public InputFile {
public:
- ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName);
+ ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName,
+ bool lazy = false, bool forceHidden = false);
+ ArrayRef<llvm::MachO::data_in_code_entry> getDataInCode() const;
+ ArrayRef<uint8_t> getOptimizationHints() const;
+ template <class LP> void parse();
+
static bool classof(const InputFile *f) { return f->kind() == ObjKind; }
+ std::string sourceFile() const;
+ // Parses line table information for diagnostics. compileUnit should be used
+ // for other purposes.
+ lld::DWARFCache *getDwarf();
+
llvm::DWARFUnit *compileUnit = nullptr;
+ std::unique_ptr<lld::DWARFCache> dwarfCache;
+ Section *addrSigSection = nullptr;
const uint32_t modTime;
+ bool forceHidden;
std::vector<ConcatInputSection *> debugSections;
- ArrayRef<llvm::MachO::data_in_code_entry> dataInCodeEntries;
+ std::vector<CallGraphEntry> callGraph;
+ llvm::DenseMap<ConcatInputSection *, FDE> fdes;
+ std::vector<AliasSymbol *> aliases;
private:
- template <class LP> void parse();
- template <class Section> void parseSections(ArrayRef<Section>);
+ llvm::once_flag initDwarf;
+ template <class LP> void parseLazy();
+ template <class SectionHeader> void parseSections(ArrayRef<SectionHeader>);
template <class LP>
void parseSymbols(ArrayRef<typename LP::section> sectionHeaders,
ArrayRef<typename LP::nlist> nList, const char *strtab,
bool subsectionsViaSymbols);
template <class NList>
- Symbol *parseNonSectionSymbol(const NList &sym, StringRef name);
- template <class Section>
- void parseRelocations(ArrayRef<Section> sectionHeaders, const Section &,
- SubsectionMap &);
+ Symbol *parseNonSectionSymbol(const NList &sym, const char *strtab);
+ template <class SectionHeader>
+ void parseRelocations(ArrayRef<SectionHeader> sectionHeaders,
+ const SectionHeader &, Section &);
void parseDebugInfo();
- void parseDataInCode();
+ void splitEhFrames(ArrayRef<uint8_t> dataArr, Section &ehFrameSection);
+ void registerCompactUnwind(Section &compactUnwindSection);
+ void registerEhFrames(Section &ehFrameSection);
};
// command-line -sectcreate file
// to the root. On the other hand, if a dylib is being directly loaded
// (through an -lfoo flag), then `umbrella` should be a nullptr.
explicit DylibFile(MemoryBufferRef mb, DylibFile *umbrella,
- bool isBundleLoader = false);
+ bool isBundleLoader, bool explicitlyLinked);
explicit DylibFile(const llvm::MachO::InterfaceFile &interface,
- DylibFile *umbrella = nullptr,
- bool isBundleLoader = false);
+ DylibFile *umbrella, bool isBundleLoader,
+ bool explicitlyLinked);
+ explicit DylibFile(DylibFile *umbrella);
void parseLoadCommands(MemoryBufferRef mb);
void parseReexports(const llvm::MachO::InterfaceFile &interface);
+ bool isReferenced() const { return numReferencedSymbols > 0; }
+ bool isExplicitlyLinked() const;
+ void setExplicitlyLinked() { explicitlyLinked = true; }
static bool classof(const InputFile *f) { return f->kind() == DylibKind; }
uint32_t compatibilityVersion = 0;
uint32_t currentVersion = 0;
int64_t ordinal = 0; // Ordinal numbering starts from 1, so 0 is a sentinel
+ unsigned numReferencedSymbols = 0;
RefState refState;
bool reexport = false;
bool forceNeeded = false;
bool forceWeakImport = false;
bool deadStrippable = false;
- bool explicitlyLinked = false;
-
- unsigned numReferencedSymbols = 0;
- bool isReferenced() const { return numReferencedSymbols > 0; }
+private:
+ bool explicitlyLinked = false; // Access via isExplicitlyLinked().
+public:
// An executable can be used as a bundle loader that will load the output
// file being linked, and that contains symbols referenced, but not
// implemented in the bundle. When used like this, it is very similar
- // to a Dylib, so we re-used the same class to represent it.
+ // to a dylib, so we've used the same class to represent it.
bool isBundleLoader;
+ // Synthetic Dylib objects created by $ld$previous symbols in this dylib.
+ // Usually empty. These synthetic dylibs won't have synthetic dylibs
+ // themselves.
+ SmallVector<DylibFile *, 2> extraDylibs;
+
private:
+ DylibFile *getSyntheticDylib(StringRef installName, uint32_t currentVersion,
+ uint32_t compatVersion);
+
bool handleLDSymbol(StringRef originalName);
void handleLDPreviousSymbol(StringRef name, StringRef originalName);
void handleLDInstallNameSymbol(StringRef name, StringRef originalName);
+ void handleLDHideSymbol(StringRef name, StringRef originalName);
void checkAppExtensionSafety(bool dylibIsAppExtensionSafe) const;
+ void parseExportedSymbols(uint32_t offset, uint32_t size);
+ void loadReexport(StringRef path, DylibFile *umbrella,
+ const llvm::MachO::InterfaceFile *currentTopLevelTapi);
+
+ llvm::DenseSet<llvm::CachedHashStringRef> hiddenSymbols;
};
// .a file
class ArchiveFile final : public InputFile {
public:
- explicit ArchiveFile(std::unique_ptr<llvm::object::Archive> &&file);
+ explicit ArchiveFile(std::unique_ptr<llvm::object::Archive> &&file,
+ bool forceHidden);
+ void addLazySymbols();
+ void fetch(const llvm::object::Archive::Symbol &);
+ // LLD normally doesn't use Error for error-handling, but the underlying
+ // Archive library does, so this is the cleanest way to wrap it.
+ Error fetch(const llvm::object::Archive::Child &, StringRef reason);
+ const llvm::object::Archive &getArchive() const { return *file; };
static bool classof(const InputFile *f) { return f->kind() == ArchiveKind; }
- void fetch(const llvm::object::Archive::Symbol &sym);
private:
std::unique_ptr<llvm::object::Archive> file;
// Keep track of children fetched from the archive by tracking
// which address offsets have been fetched already.
llvm::DenseSet<uint64_t> seen;
+ // Load all symbols with hidden visibility (-load_hidden).
+ bool forceHidden;
};
class BitcodeFile final : public InputFile {
public:
explicit BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
- uint64_t offsetInArchive);
+ uint64_t offsetInArchive, bool lazy = false,
+ bool forceHidden = false);
static bool classof(const InputFile *f) { return f->kind() == BitcodeKind; }
+ void parse();
std::unique_ptr<llvm::lto::InputFile> obj;
+ bool forceHidden;
+
+private:
+ void parseLazy();
};
extern llvm::SetVector<InputFile *> inputFiles;
+extern llvm::DenseMap<llvm::CachedHashStringRef, MemoryBufferRef> cachedReads;
+
+std::optional<MemoryBufferRef> readFile(StringRef path);
-llvm::Optional<MemoryBufferRef> readFile(StringRef path);
+void extract(InputFile &file, StringRef reason);
namespace detail {
return detail::findCommands<CommandType>(anyHdr, 0, types...);
}
+std::string replaceThinLTOSuffix(StringRef path);
} // namespace macho
std::string toString(const macho::InputFile *file);
+std::string toString(const macho::Section &);
} // namespace lld
#endif
#include "Target.h"
#include "UnwindInfoSection.h"
#include "Writer.h"
+
+#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/xxhash.h"
using namespace lld;
using namespace lld::macho;
+// Verify ConcatInputSection's size on 64-bit builds. The size of std::vector
+// can differ based on STL debug levels (e.g. iterator debugging on MSVC's STL),
+// so account for that.
+static_assert(sizeof(void *) != 8 ||
+ sizeof(ConcatInputSection) == sizeof(std::vector<Reloc>) + 88,
+ "Try to minimize ConcatInputSection's size, we create many "
+ "instances of it");
+
std::vector<ConcatInputSection *> macho::inputSections;
uint64_t InputSection::getFileSize() const {
return sym->getVA();
}
-// ICF needs to hash any section that might potentially be duplicated so
-// that it can match on content rather than identity.
-bool ConcatInputSection::isHashableForICF() const {
- switch (sectionType(getFlags())) {
- case S_REGULAR:
- return true;
- case S_CSTRING_LITERALS:
- case S_4BYTE_LITERALS:
- case S_8BYTE_LITERALS:
- case S_16BYTE_LITERALS:
- case S_LITERAL_POINTERS:
- llvm_unreachable("found unexpected literal type in ConcatInputSection");
- case S_ZEROFILL:
- case S_GB_ZEROFILL:
- case S_NON_LAZY_SYMBOL_POINTERS:
- case S_LAZY_SYMBOL_POINTERS:
- case S_SYMBOL_STUBS:
- case S_MOD_INIT_FUNC_POINTERS:
- case S_MOD_TERM_FUNC_POINTERS:
- case S_COALESCED:
- case S_INTERPOSING:
- case S_DTRACE_DOF:
- case S_LAZY_DYLIB_SYMBOL_POINTERS:
- case S_THREAD_LOCAL_REGULAR:
- case S_THREAD_LOCAL_ZEROFILL:
- case S_THREAD_LOCAL_VARIABLES:
- case S_THREAD_LOCAL_VARIABLE_POINTERS:
- case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS:
- return false;
- default:
- llvm_unreachable("Section type");
+const Defined *InputSection::getContainingSymbol(uint64_t off) const {
+ auto *nextSym = llvm::upper_bound(
+ symbols, off, [](uint64_t a, const Defined *b) { return a < b->value; });
+ if (nextSym == symbols.begin())
+ return nullptr;
+ return *std::prev(nextSym);
+}
+
+std::string InputSection::getLocation(uint64_t off) const {
+ // First, try to find a symbol that's near the offset. Use it as a reference
+ // point.
+ if (auto *sym = getContainingSymbol(off))
+ return (toString(getFile()) + ":(symbol " + toString(*sym) + "+0x" +
+ Twine::utohexstr(off - sym->value) + ")")
+ .str();
+
+ // If that fails, use the section itself as a reference point.
+ for (const Subsection &subsec : section.subsections) {
+ if (subsec.isec == this) {
+ off += subsec.offset;
+ break;
+ }
}
+
+ return (toString(getFile()) + ":(" + getName() + "+0x" +
+ Twine::utohexstr(off) + ")")
+ .str();
}
-void ConcatInputSection::hashForICF() {
- assert(data.data()); // zeroFill section data has nullptr with non-zero size
- assert(icfEqClass[0] == 0); // don't overwrite a unique ID!
- // Turn-on the top bit to guarantee that valid hashes have no collisions
- // with the small-integer unique IDs for ICF-ineligible sections
- icfEqClass[0] = xxHash64(data) | (1ull << 63);
+std::string InputSection::getSourceLocation(uint64_t off) const {
+ auto *obj = dyn_cast_or_null<ObjFile>(getFile());
+ if (!obj)
+ return {};
+
+ DWARFCache *dwarf = obj->getDwarf();
+ if (!dwarf)
+ return std::string();
+
+ for (const Subsection &subsec : section.subsections) {
+ if (subsec.isec == this) {
+ off += subsec.offset;
+ break;
+ }
+ }
+
+ auto createMsg = [&](StringRef path, unsigned line) {
+ std::string filename = sys::path::filename(path).str();
+ std::string lineStr = (":" + Twine(line)).str();
+ if (filename == path)
+ return filename + lineStr;
+ return (filename + lineStr + " (" + path + lineStr + ")").str();
+ };
+
+ // First, look up a function for a given offset.
+ if (std::optional<DILineInfo> li = dwarf->getDILineInfo(
+ section.addr + off, object::SectionedAddress::UndefSection))
+ return createMsg(li->FileName, li->Line);
+
+ // If it failed, look up again as a variable.
+ if (const Defined *sym = getContainingSymbol(off)) {
+ // Symbols are generally prefixed with an underscore, which is not included
+ // in the debug information.
+ StringRef symName = sym->getName();
+ if (!symName.empty() && symName[0] == '_')
+ symName = symName.substr(1);
+
+ if (std::optional<std::pair<std::string, unsigned>> fileLine =
+ dwarf->getVariableLoc(symName))
+ return createMsg(fileLine->first, fileLine->second);
+ }
+
+ // Try to get the source file's name from the DWARF information.
+ if (obj->compileUnit)
+ return obj->sourceFile();
+
+ return {};
}
void ConcatInputSection::foldIdentical(ConcatInputSection *copy) {
align = std::max(align, copy->align);
copy->live = false;
copy->wasCoalesced = true;
- numRefs += copy->numRefs;
- copy->numRefs = 0;
copy->replacement = this;
+ for (auto ©Sym : copy->symbols) {
+ copySym->wasIdenticalCodeFolded = true;
+ copySym->size = 0;
+ }
+
+ symbols.insert(symbols.end(), copy->symbols.begin(), copy->symbols.end());
+ copy->symbols.clear();
+
+ // Remove duplicate compact unwind info for symbols at the same address.
+ if (symbols.empty())
+ return;
+ for (auto it = symbols.begin() + 1; it != symbols.end(); ++it) {
+ assert((*it)->value == 0);
+ (*it)->unwindEntry = nullptr;
+ }
}
void ConcatInputSection::writeTo(uint8_t *buf) {
const Reloc &r = relocs[i];
uint8_t *loc = buf + r.offset;
uint64_t referentVA = 0;
+
+ const bool needsFixup = config->emitChainedFixups &&
+ target->hasAttr(r.type, RelocAttrBits::UNSIGNED);
if (target->hasAttr(r.type, RelocAttrBits::SUBTRAHEND)) {
const Symbol *fromSym = r.referent.get<Symbol *>();
const Reloc &minuend = relocs[++i];
if (target->hasAttr(r.type, RelocAttrBits::LOAD) &&
!referentSym->isInGot())
target->relaxGotLoad(loc, r.type);
+ // For dtrace symbols, do not handle them as normal undefined symbols
+ if (referentSym->getName().startswith("___dtrace_")) {
+ // Change dtrace call site to pre-defined instructions
+ target->handleDtraceReloc(referentSym, r, loc);
+ continue;
+ }
referentVA = resolveSymbolVA(referentSym, r.type) + r.addend;
- if (isThreadLocalVariables(getFlags())) {
+ if (isThreadLocalVariables(getFlags()) && isa<Defined>(referentSym)) {
// References from thread-local variable sections are treated as offsets
// relative to the start of the thread-local data memory area, which
// is initialized via copying all the TLV data sections (which are all
// contiguous).
- if (isa<Defined>(referentSym))
- referentVA -= firstTLVDataSection->addr;
+ referentVA -= firstTLVDataSection->addr;
+ } else if (needsFixup) {
+ writeChainedFixup(loc, referentSym, r.addend);
+ continue;
}
} else if (auto *referentIsec = r.referent.dyn_cast<InputSection *>()) {
assert(!::shouldOmitFromOutput(referentIsec));
referentVA = referentIsec->getVA(r.addend);
+
+ if (needsFixup) {
+ writeChainedRebase(loc, referentVA);
+ continue;
+ }
}
target->relocateOne(loc, r, referentVA, getVA() + r.offset);
}
}
+ConcatInputSection *macho::makeSyntheticInputSection(StringRef segName,
+ StringRef sectName,
+ uint32_t flags,
+ ArrayRef<uint8_t> data,
+ uint32_t align) {
+ Section §ion =
+ *make<Section>(/*file=*/nullptr, segName, sectName, flags, /*addr=*/0);
+ auto isec = make<ConcatInputSection>(section, data, align);
+ section.subsections.push_back({0, isec});
+ return isec;
+}
+
void CStringInputSection::splitIntoPieces() {
size_t off = 0;
StringRef s = toStringRef(data);
while (!s.empty()) {
size_t end = s.find(0);
if (end == StringRef::npos)
- fatal(toString(this) + ": string is not null terminated");
- size_t size = end + 1;
- uint32_t hash = config->dedupLiterals ? xxHash64(s.substr(0, size)) : 0;
+ fatal(getLocation(off) + ": string is not null terminated");
+ uint32_t hash = deduplicateLiterals ? xxHash64(s.take_front(end)) : 0;
pieces.emplace_back(off, hash);
+ size_t size = end + 1; // include null terminator
s = s.substr(size);
off += size;
}
return piece.outSecOff + addend;
}
-WordLiteralInputSection::WordLiteralInputSection(StringRef segname,
- StringRef name,
- InputFile *file,
+WordLiteralInputSection::WordLiteralInputSection(const Section §ion,
ArrayRef<uint8_t> data,
- uint32_t align, uint32_t flags)
- : InputSection(WordLiteralKind, segname, name, file, data, align, flags) {
- switch (sectionType(flags)) {
+ uint32_t align)
+ : InputSection(WordLiteralKind, section, data, align) {
+ switch (sectionType(getFlags())) {
case S_4BYTE_LITERALS:
power2LiteralSize = 2;
break;
uint64_t WordLiteralInputSection::getOffset(uint64_t off) const {
auto *osec = cast<WordLiteralSection>(parent);
- const uint8_t *buf = data.data();
+ const uintptr_t buf = reinterpret_cast<uintptr_t>(data.data());
switch (sectionType(getFlags())) {
case S_4BYTE_LITERALS:
- return osec->getLiteral4Offset(buf + off);
+ return osec->getLiteral4Offset(buf + (off & ~3LLU)) | (off & 3);
case S_8BYTE_LITERALS:
- return osec->getLiteral8Offset(buf + off);
+ return osec->getLiteral8Offset(buf + (off & ~7LLU)) | (off & 7);
case S_16BYTE_LITERALS:
- return osec->getLiteral16Offset(buf + off);
+ return osec->getLiteral16Offset(buf + (off & ~15LLU)) | (off & 15);
default:
llvm_unreachable("invalid literal section type");
}
isec->getSegName() == segment_names::data;
}
+bool macho::isClassRefsSection(const InputSection *isec) {
+ return isec->getName() == section_names::objcClassRefs &&
+ isec->getSegName() == segment_names::data;
+}
+
+bool macho::isSelRefsSection(const InputSection *isec) {
+ return isec->getName() == section_names::objcSelrefs &&
+ isec->getSegName() == segment_names::data;
+}
+
+bool macho::isEhFrameSection(const InputSection *isec) {
+ return isec->getName() == section_names::ehFrame &&
+ isec->getSegName() == segment_names::text;
+}
+
+bool macho::isGccExceptTabSection(const InputSection *isec) {
+ return isec->getName() == section_names::gccExceptTab &&
+ isec->getSegName() == segment_names::text;
+}
+
std::string lld::toString(const InputSection *isec) {
return (toString(isec->getFile()) + ":(" + isec->getName() + ")").str();
}
#include "Config.h"
#include "Relocations.h"
+#include "Symbols.h"
#include "lld/Common/LLVM.h"
#include "lld/Common/Memory.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/CachedHashString.h"
+#include "llvm/ADT/TinyPtrVector.h"
#include "llvm/BinaryFormat/MachO.h"
namespace lld {
class InputFile;
class OutputSection;
-class Defined;
class InputSection {
public:
- enum Kind {
+ enum Kind : uint8_t {
ConcatKind,
CStringLiteralKind,
WordLiteralKind,
};
- Kind kind() const { return shared->sectionKind; }
+ Kind kind() const { return sectionKind; }
virtual ~InputSection() = default;
virtual uint64_t getSize() const { return data.size(); }
- InputFile *getFile() const { return shared->file; }
- StringRef getName() const { return shared->name; }
- StringRef getSegName() const { return shared->segname; }
- uint32_t getFlags() const { return shared->flags; }
+ virtual bool empty() const { return data.empty(); }
+ InputFile *getFile() const { return section.file; }
+ StringRef getName() const { return section.name; }
+ StringRef getSegName() const { return section.segname; }
+ uint32_t getFlags() const { return section.flags; }
uint64_t getFileSize() const;
// Translates \p off -- an offset relative to this InputSection -- into an
// offset from the beginning of its parent OutputSection.
virtual uint64_t getOffset(uint64_t off) const = 0;
// The offset from the beginning of the file.
uint64_t getVA(uint64_t off) const;
+ // Return a user-friendly string for use in diagnostics.
+ // Format: /path/to/object.o:(symbol _func+0x123)
+ std::string getLocation(uint64_t off) const;
+ // Return the source line corresponding to an address, or the empty string.
+ // Format: Source.cpp:123 (/path/to/Source.cpp:123)
+ std::string getSourceLocation(uint64_t off) const;
// Whether the data at \p off in this InputSection is live.
virtual bool isLive(uint64_t off) const = 0;
virtual void markLive(uint64_t off) = 0;
virtual InputSection *canonical() { return this; }
+ virtual const InputSection *canonical() const { return this; }
- OutputSection *parent = nullptr;
+protected:
+ InputSection(Kind kind, const Section §ion, ArrayRef<uint8_t> data,
+ uint32_t align)
+ : sectionKind(kind), keepUnique(false), hasAltEntry(false), align(align),
+ data(data), section(section) {}
- uint32_t align = 1;
- uint32_t callSiteCount : 31;
+ InputSection(const InputSection &rhs)
+ : sectionKind(rhs.sectionKind), keepUnique(false), hasAltEntry(false),
+ align(rhs.align), data(rhs.data), section(rhs.section) {}
+
+ Kind sectionKind;
+
+public:
// is address assigned?
- uint32_t isFinal : 1;
+ bool isFinal = false;
+ // keep the address of the symbol(s) in this section unique in the final
+ // binary ?
+ bool keepUnique : 1;
+ // Does this section have symbols at offsets other than zero? (NOTE: only
+ // applies to ConcatInputSections.)
+ bool hasAltEntry : 1;
+ uint32_t align = 1;
+ OutputSection *parent = nullptr;
ArrayRef<uint8_t> data;
std::vector<Reloc> relocs;
+ // The symbols that belong to this InputSection, sorted by value. With
+ // .subsections_via_symbols, there is typically only one element here.
+ llvm::TinyPtrVector<Defined *> symbols;
protected:
- // The fields in this struct are immutable. Since we create a lot of
- // InputSections with identical values for them (due to
- // .subsections_via_symbols), factoring them out into a shared struct reduces
- // memory consumption and makes copying cheaper.
- struct Shared {
- InputFile *file;
- StringRef name;
- StringRef segname;
- uint32_t flags;
- Kind sectionKind;
- Shared(InputFile *file, StringRef name, StringRef segname, uint32_t flags,
- Kind kind)
- : file(file), name(name), segname(segname), flags(flags),
- sectionKind(kind) {}
- };
+ const Section §ion;
- InputSection(Kind kind, StringRef segname, StringRef name)
- : callSiteCount(0), isFinal(false),
- shared(make<Shared>(nullptr, name, segname, 0, kind)) {}
-
- InputSection(Kind kind, StringRef segname, StringRef name, InputFile *file,
- ArrayRef<uint8_t> data, uint32_t align, uint32_t flags)
- : align(align), callSiteCount(0), isFinal(false), data(data),
- shared(make<Shared>(file, name, segname, flags, kind)) {}
-
- const Shared *const shared;
+ const Defined *getContainingSymbol(uint64_t off) const;
};
// ConcatInputSections are combined into (Concat)OutputSections through simple
// contents merged before output.
class ConcatInputSection final : public InputSection {
public:
- ConcatInputSection(StringRef segname, StringRef name)
- : InputSection(ConcatKind, segname, name) {}
-
- ConcatInputSection(StringRef segname, StringRef name, InputFile *file,
- ArrayRef<uint8_t> data, uint32_t align = 1,
- uint32_t flags = 0)
- : InputSection(ConcatKind, segname, name, file, data, align, flags) {}
+ ConcatInputSection(const Section §ion, ArrayRef<uint8_t> data,
+ uint32_t align = 1)
+ : InputSection(ConcatKind, section, data, align) {}
uint64_t getOffset(uint64_t off) const override { return outSecOff + off; }
uint64_t getVA() const { return InputSection::getVA(0); }
// ConcatInputSections are entirely live or dead, so the offset is irrelevant.
bool isLive(uint64_t off) const override { return live; }
void markLive(uint64_t off) override { live = true; }
- bool isCoalescedWeak() const { return wasCoalesced && numRefs == 0; }
+ bool isCoalescedWeak() const { return wasCoalesced && symbols.empty(); }
bool shouldOmitFromOutput() const { return !live || isCoalescedWeak(); }
- bool isHashableForICF() const;
- void hashForICF();
void writeTo(uint8_t *buf);
void foldIdentical(ConcatInputSection *redundant);
- InputSection *canonical() override {
+ ConcatInputSection *canonical() override {
+ return replacement ? replacement : this;
+ }
+ const InputSection *canonical() const override {
return replacement ? replacement : this;
}
}
// Points to the surviving section after this one is folded by ICF
- InputSection *replacement = nullptr;
+ ConcatInputSection *replacement = nullptr;
// Equivalence-class ID for ICF
- uint64_t icfEqClass[2] = {0, 0};
+ uint32_t icfEqClass[2] = {0, 0};
// With subsections_via_symbols, most symbols have their own InputSection,
// and for weak symbols (e.g. from inline functions), only the
// first and not copied to the output.
bool wasCoalesced = false;
bool live = !config->deadStrip;
- // How many symbols refer to this InputSection.
- uint32_t numRefs = 0;
+ bool hasCallSites = false;
// This variable has two usages. Initially, it represents the input order.
// After assignAddresses is called, it represents the offset from the
// beginning of the output section this section was assigned to.
uint64_t outSecOff = 0;
};
-// Verify ConcatInputSection's size on 64-bit builds.
-static_assert(sizeof(int) != 8 || sizeof(ConcatInputSection) == 112,
- "Try to minimize ConcatInputSection's size, we create many "
- "instances of it");
+// Initialize a fake InputSection that does not belong to any InputFile.
+ConcatInputSection *makeSyntheticInputSection(StringRef segName,
+ StringRef sectName,
+ uint32_t flags = 0,
+ ArrayRef<uint8_t> data = {},
+ uint32_t align = 1);
// Helper functions to make it easy to sprinkle asserts.
// conservative behavior we can certainly implement that.
class CStringInputSection final : public InputSection {
public:
- CStringInputSection(StringRef segname, StringRef name, InputFile *file,
- ArrayRef<uint8_t> data, uint32_t align, uint32_t flags)
- : InputSection(CStringLiteralKind, segname, name, file, data, align,
- flags) {}
+ CStringInputSection(const Section §ion, ArrayRef<uint8_t> data,
+ uint32_t align, bool dedupLiterals)
+ : InputSection(CStringLiteralKind, section, data, align),
+ deduplicateLiterals(dedupLiterals) {}
+
uint64_t getOffset(uint64_t off) const override;
bool isLive(uint64_t off) const override { return getStringPiece(off).live; }
void markLive(uint64_t off) override { getStringPiece(off).live = true; }
LLVM_ATTRIBUTE_ALWAYS_INLINE
StringRef getStringRef(size_t i) const {
size_t begin = pieces[i].inSecOff;
+ // The endpoint should be *at* the null terminator, not after. This matches
+ // the behavior of StringRef(const char *Str).
size_t end =
- (pieces.size() - 1 == i) ? data.size() : pieces[i + 1].inSecOff;
+ ((pieces.size() - 1 == i) ? data.size() : pieces[i + 1].inSecOff) - 1;
return toStringRef(data.slice(begin, end - begin));
}
// string merging is enabled, so we want to inline.
LLVM_ATTRIBUTE_ALWAYS_INLINE
llvm::CachedHashStringRef getCachedHashStringRef(size_t i) const {
- assert(config->dedupLiterals);
+ assert(deduplicateLiterals);
return {getStringRef(i), pieces[i].hash};
}
return isec->kind() == CStringLiteralKind;
}
+ bool deduplicateLiterals = false;
std::vector<StringPiece> pieces;
};
class WordLiteralInputSection final : public InputSection {
public:
- WordLiteralInputSection(StringRef segname, StringRef name, InputFile *file,
- ArrayRef<uint8_t> data, uint32_t align,
- uint32_t flags);
+ WordLiteralInputSection(const Section §ion, ArrayRef<uint8_t> data,
+ uint32_t align);
uint64_t getOffset(uint64_t off) const override;
bool isLive(uint64_t off) const override {
return live[off >> power2LiteralSize];
}
- void markLive(uint64_t off) override { live[off >> power2LiteralSize] = 1; }
+ void markLive(uint64_t off) override {
+ live[off >> power2LiteralSize] = true;
+ }
static bool classof(const InputSection *isec) {
return isec->kind() == WordLiteralKind;
}
bool isCodeSection(const InputSection *);
-
bool isCfStringSection(const InputSection *);
+bool isClassRefsSection(const InputSection *);
+bool isSelRefsSection(const InputSection *);
+bool isEhFrameSection(const InputSection *);
+bool isGccExceptTabSection(const InputSection *);
extern std::vector<ConcatInputSection *> inputSections;
constexpr const char bitcodeBundle[] = "__bundle";
constexpr const char cString[] = "__cstring";
constexpr const char cfString[] = "__cfstring";
+constexpr const char cgProfile[] = "__cg_profile";
+constexpr const char chainFixups[] = "__chainfixups";
constexpr const char codeSignature[] = "__code_signature";
constexpr const char common[] = "__common";
constexpr const char compactUnwind[] = "__compact_unwind";
constexpr const char data[] = "__data";
constexpr const char debugAbbrev[] = "__debug_abbrev";
constexpr const char debugInfo[] = "__debug_info";
+constexpr const char debugLine[] = "__debug_line";
constexpr const char debugStr[] = "__debug_str";
+constexpr const char debugStrOffs[] = "__debug_str_offs";
constexpr const char ehFrame[] = "__eh_frame";
+constexpr const char gccExceptTab[] = "__gcc_except_tab";
constexpr const char export_[] = "__export";
constexpr const char dataInCode[] = "__data_in_code";
constexpr const char functionStarts[] = "__func_starts";
constexpr const char got[] = "__got";
constexpr const char header[] = "__mach_header";
constexpr const char indirectSymbolTable[] = "__ind_sym_tab";
+constexpr const char initOffsets[] = "__init_offsets";
constexpr const char const_[] = "__const";
constexpr const char lazySymbolPtr[] = "__la_symbol_ptr";
constexpr const char lazyBinding[] = "__lazy_binding";
constexpr const char nonLazySymbolPtr[] = "__nl_symbol_ptr";
constexpr const char objcCatList[] = "__objc_catlist";
constexpr const char objcClassList[] = "__objc_classlist";
+constexpr const char objcClassRefs[] = "__objc_classrefs";
constexpr const char objcConst[] = "__objc_const";
-constexpr const char objcImageInfo[] = "__objc_imageinfo";
+constexpr const char objCImageInfo[] = "__objc_imageinfo";
+constexpr const char objcStubs[] = "__objc_stubs";
+constexpr const char objcSelrefs[] = "__objc_selrefs";
+constexpr const char objcMethname[] = "__objc_methname";
constexpr const char objcNonLazyCatList[] = "__objc_nlcatlist";
constexpr const char objcNonLazyClassList[] = "__objc_nlclslist";
constexpr const char objcProtoList[] = "__objc_protolist";
constexpr const char unwindInfo[] = "__unwind_info";
constexpr const char weakBinding[] = "__weak_binding";
constexpr const char zeroFill[] = "__zerofill";
+constexpr const char addrSig[] = "__llvm_addrsig";
} // namespace section_names
#include "Target.h"
#include "lld/Common/Args.h"
-#include "lld/Common/ErrorHandler.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Strings.h"
#include "lld/Common/TargetOptionsCommandFlags.h"
-#include "llvm/LTO/Caching.h"
+#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/LTO/Config.h"
#include "llvm/LTO/LTO.h"
+#include "llvm/Support/Caching.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm::MachO;
using namespace llvm::sys;
+// Creates an empty file to store a list of object files for final
+// linking of distributed ThinLTO.
+static std::unique_ptr<raw_fd_ostream> openFile(StringRef file) {
+ std::error_code ec;
+ auto ret =
+ std::make_unique<raw_fd_ostream>(file, ec, sys::fs::OpenFlags::OF_None);
+ if (ec) {
+ error("cannot open " + file + ": " + ec.message());
+ return nullptr;
+ }
+ return ret;
+}
+
+static std::string getThinLTOOutputFile(StringRef modulePath) {
+ return lto::getThinLTOOutputFile(
+ std::string(modulePath), std::string(config->thinLTOPrefixReplace.first),
+ std::string(config->thinLTOPrefixReplace.second));
+}
+
static lto::Config createConfig() {
lto::Config c;
c.Options = initTargetOptionsFromCodeGenFlags();
+ c.Options.EmitAddrsig = config->icfLevel == ICFLevel::safe;
+ for (StringRef C : config->mllvmOpts)
+ c.MllvmArgs.emplace_back(C.str());
c.CodeModel = getCodeModelFromCMModel();
c.CPU = getCPUStr();
c.MAttrs = getMAttrs();
- c.UseNewPM = config->ltoNewPassManager;
+ c.DiagHandler = diagnosticHandler;
c.PreCodeGenPassesHook = [](legacy::PassManager &pm) {
pm.add(createObjCARCContractPass());
};
+
+ c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty();
+
c.TimeTraceEnabled = config->timeTraceEnabled;
c.TimeTraceGranularity = config->timeTraceGranularity;
c.OptLevel = config->ltoo;
return c;
}
+// If `originalPath` exists, hardlinks `path` to `originalPath`. If that fails,
+// or `originalPath` is not set, saves `buffer` to `path`.
+static void saveOrHardlinkBuffer(StringRef buffer, const Twine &path,
+ std::optional<StringRef> originalPath) {
+ if (originalPath) {
+ auto err = fs::create_hard_link(*originalPath, path);
+ if (!err)
+ return;
+ }
+ saveBuffer(buffer, path);
+}
+
BitcodeCompiler::BitcodeCompiler() {
- lto::ThinBackend backend = lto::createInProcessThinBackend(
- heavyweight_hardware_concurrency(config->thinLTOJobs));
+ // Initialize indexFile.
+ if (!config->thinLTOIndexOnlyArg.empty())
+ indexFile = openFile(config->thinLTOIndexOnlyArg);
+
+ // Initialize ltoObj.
+ lto::ThinBackend backend;
+ auto onIndexWrite = [&](StringRef S) { thinIndices.erase(S); };
+ if (config->thinLTOIndexOnly) {
+ backend = lto::createWriteIndexesThinBackend(
+ std::string(config->thinLTOPrefixReplace.first),
+ std::string(config->thinLTOPrefixReplace.second),
+ config->thinLTOEmitImportsFiles, indexFile.get(), onIndexWrite);
+ } else {
+ backend = lto::createInProcessThinBackend(
+ llvm::heavyweight_hardware_concurrency(config->thinLTOJobs),
+ onIndexWrite, config->thinLTOEmitIndexFiles,
+ config->thinLTOEmitImportsFiles);
+ }
+
ltoObj = std::make_unique<lto::LTO>(createConfig(), backend);
}
void BitcodeCompiler::add(BitcodeFile &f) {
- ArrayRef<lto::InputFile::Symbol> objSyms = f.obj->symbols();
+ lto::InputFile &obj = *f.obj;
+
+ if (config->thinLTOEmitIndexFiles)
+ thinIndices.insert(obj.getName());
+
+ ArrayRef<lto::InputFile::Symbol> objSyms = obj.symbols();
std::vector<lto::SymbolResolution> resols;
resols.reserve(objSyms.size());
// Provide a resolution to the LTO API for each symbol.
+ bool exportDynamic =
+ config->outputType != MH_EXECUTE || config->exportDynamic;
auto symIt = f.symbols.begin();
for (const lto::InputFile::Symbol &objSym : objSyms) {
resols.emplace_back();
// be removed.
r.Prevailing = !objSym.isUndefined() && sym->getFile() == &f;
- // FIXME: What about other output types? And we can probably be less
- // restrictive with -flat_namespace, but it's an infrequent use case.
- // FIXME: Honor config->exportDynamic.
- r.VisibleToRegularObj = config->outputType != MH_EXECUTE ||
- config->namespaceKind == NamespaceKind::flat ||
- sym->isUsedInRegularObj;
+ if (const auto *defined = dyn_cast<Defined>(sym)) {
+ r.ExportDynamic =
+ defined->isExternal() && !defined->privateExtern && exportDynamic;
+ r.FinalDefinitionInLinkageUnit =
+ !defined->isExternalWeakDef() && !defined->interposable;
+ } else if (const auto *common = dyn_cast<CommonSymbol>(sym)) {
+ r.ExportDynamic = !common->privateExtern && exportDynamic;
+ r.FinalDefinitionInLinkageUnit = true;
+ }
+
+ r.VisibleToRegularObj =
+ sym->isUsedInRegularObj || (r.Prevailing && r.ExportDynamic);
// Un-define the symbol so that we don't get duplicate symbol errors when we
// load the ObjFile emitted by LTO compilation.
if (r.Prevailing)
replaceSymbol<Undefined>(sym, sym->getName(), sym->getFile(),
- RefState::Strong);
+ RefState::Strong, /*wasBitcodeSymbol=*/true);
// TODO: set the other resolution configs properly
}
checkError(ltoObj->add(std::move(f.obj), resols));
}
+// If LazyObjFile has not been added to link, emit empty index files.
+// This is needed because this is what GNU gold plugin does and we have a
+// distributed build system that depends on that behavior.
+static void thinLTOCreateEmptyIndexFiles() {
+ DenseSet<StringRef> linkedBitCodeFiles;
+ for (InputFile *file : inputFiles)
+ if (auto *f = dyn_cast<BitcodeFile>(file))
+ if (!f->lazy)
+ linkedBitCodeFiles.insert(f->getName());
+
+ for (InputFile *file : inputFiles) {
+ if (auto *f = dyn_cast<BitcodeFile>(file)) {
+ if (!f->lazy)
+ continue;
+ if (linkedBitCodeFiles.contains(f->getName()))
+ continue;
+ std::string path =
+ replaceThinLTOSuffix(getThinLTOOutputFile(f->obj->getName()));
+ std::unique_ptr<raw_fd_ostream> os = openFile(path + ".thinlto.bc");
+ if (!os)
+ continue;
+
+ ModuleSummaryIndex m(/*HaveGVs=*/false);
+ m.setSkipModuleByDistributedBackend();
+ writeIndexToFile(m, *os);
+ if (config->thinLTOEmitImportsFiles)
+ openFile(path + ".imports");
+ }
+ }
+}
+
// Merge all the bitcode files we have seen, codegen the result
// and return the resulting ObjectFile(s).
std::vector<ObjFile *> BitcodeCompiler::compile() {
// The -cache_path_lto option specifies the path to a directory in which
// to cache native object files for ThinLTO incremental builds. If a path was
// specified, configure LTO to use it as the cache directory.
- lto::NativeObjectCache cache;
+ FileCache cache;
if (!config->thinLTOCacheDir.empty())
- cache = check(
- lto::localCache(config->thinLTOCacheDir,
- [&](size_t task, std::unique_ptr<MemoryBuffer> mb) {
- files[task] = std::move(mb);
- }));
+ cache = check(localCache("ThinLTO", "Thin", config->thinLTOCacheDir,
+ [&](size_t task, const Twine &moduleName,
+ std::unique_ptr<MemoryBuffer> mb) {
+ files[task] = std::move(mb);
+ }));
checkError(ltoObj->run(
- [&](size_t task) {
- return std::make_unique<lto::NativeObjectStream>(
+ [&](size_t task, const Twine &moduleName) {
+ return std::make_unique<CachedFileStream>(
std::make_unique<raw_svector_ostream>(buf[task]));
},
cache));
- if (!config->thinLTOCacheDir.empty())
- pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy);
+ // Emit empty index files for non-indexed files
+ for (StringRef s : thinIndices) {
+ std::string path = getThinLTOOutputFile(s);
+ openFile(path + ".thinlto.bc");
+ if (config->thinLTOEmitImportsFiles)
+ openFile(path + ".imports");
+ }
+
+ if (config->thinLTOEmitIndexFiles)
+ thinLTOCreateEmptyIndexFiles();
+
+ // In ThinLTO mode, Clang passes a temporary directory in -object_path_lto,
+ // while the argument is a single file in FullLTO mode.
+ bool objPathIsDir = true;
+ if (!config->ltoObjPath.empty()) {
+ if (std::error_code ec = fs::create_directories(config->ltoObjPath))
+ fatal("cannot create LTO object path " + config->ltoObjPath + ": " +
+ ec.message());
+
+ if (!fs::is_directory(config->ltoObjPath)) {
+ objPathIsDir = false;
+ unsigned objCount =
+ count_if(buf, [](const SmallString<0> &b) { return !b.empty(); });
+ if (objCount > 1)
+ fatal("-object_path_lto must specify a directory when using ThinLTO");
+ }
+ }
+
+ auto outputFilePath = [objPathIsDir](int i) {
+ SmallString<261> filePath("/tmp/lto.tmp");
+ if (!config->ltoObjPath.empty()) {
+ filePath = config->ltoObjPath;
+ if (objPathIsDir)
+ path::append(filePath, Twine(i) + "." +
+ getArchitectureName(config->arch()) +
+ ".lto.o");
+ }
+ return filePath;
+ };
- if (config->saveTemps) {
- if (!buf[0].empty())
- saveBuffer(buf[0], config->outputFile + ".lto.o");
- for (unsigned i = 1; i != maxTasks; ++i)
- saveBuffer(buf[i], config->outputFile + Twine(i) + ".lto.o");
+ // ThinLTO with index only option is required to generate only the index
+ // files. After that, we exit from linker and ThinLTO backend runs in a
+ // distributed environment.
+ if (config->thinLTOIndexOnly) {
+ if (!config->ltoObjPath.empty())
+ saveBuffer(buf[0], outputFilePath(0));
+ if (indexFile)
+ indexFile->close();
+ return {};
}
- if (!config->ltoObjPath.empty())
- fs::create_directories(config->ltoObjPath);
+ if (!config->thinLTOCacheDir.empty())
+ pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy, files);
std::vector<ObjFile *> ret;
- for (unsigned i = 0; i != maxTasks; ++i) {
- if (buf[i].empty())
+ for (unsigned i = 0; i < maxTasks; ++i) {
+ // Get the native object contents either from the cache or from memory. Do
+ // not use the cached MemoryBuffer directly to ensure dsymutil does not
+ // race with the cache pruner.
+ StringRef objBuf;
+ std::optional<StringRef> cachePath;
+ if (files[i]) {
+ objBuf = files[i]->getBuffer();
+ cachePath = files[i]->getBufferIdentifier();
+ } else {
+ objBuf = buf[i];
+ }
+ if (objBuf.empty())
continue;
- SmallString<261> filePath("/tmp/lto.tmp");
+
+ // FIXME: should `saveTemps` and `ltoObjPath` use the same file name?
+ if (config->saveTemps)
+ saveBuffer(objBuf,
+ config->outputFile + ((i == 0) ? "" : Twine(i)) + ".lto.o");
+
+ auto filePath = outputFilePath(i);
uint32_t modTime = 0;
if (!config->ltoObjPath.empty()) {
- filePath = config->ltoObjPath;
- path::append(filePath, Twine(i) + "." +
- getArchitectureName(config->arch()) +
- ".lto.o");
- saveBuffer(buf[i], filePath);
+ saveOrHardlinkBuffer(objBuf, filePath, cachePath);
modTime = getModTime(filePath);
}
ret.push_back(make<ObjFile>(
- MemoryBufferRef(buf[i], saver.save(filePath.str())), modTime, ""));
+ MemoryBufferRef(objBuf, saver().save(filePath.str())), modTime, ""));
}
- for (std::unique_ptr<MemoryBuffer> &file : files)
- if (file)
- ret.push_back(make<ObjFile>(*file, 0, ""));
+
return ret;
}
#ifndef LLD_MACHO_LTO_H
#define LLD_MACHO_LTO_H
+#include "lld/Common/LLVM.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/raw_ostream.h"
#include <memory>
#include <vector>
-namespace llvm {
-namespace lto {
+namespace llvm::lto {
class LTO;
-} // namespace lto
-} // namespace llvm
+} // namespace llvm::lto
-namespace lld {
-namespace macho {
+namespace lld::macho {
class BitcodeFile;
class ObjFile;
std::unique_ptr<llvm::lto::LTO> ltoObj;
std::vector<llvm::SmallString<0>> buf;
std::vector<std::unique_ptr<llvm::MemoryBuffer>> files;
+ std::unique_ptr<llvm::raw_fd_ostream> indexFile;
+ llvm::DenseSet<StringRef> thinIndices;
};
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "llvm/Support/Endian.h"
-namespace lld {
-
-namespace structs {
+namespace lld::structs {
struct nlist_64 {
llvm::support::ulittle32_t n_strx;
llvm::support::ulittle64_t stacksize;
};
-} // namespace structs
-
-} // namespace lld
+} // namespace lld::structs
#endif
//
//===----------------------------------------------------------------------===//
//
-// This file implements the -map option. It shows lists in order and
-// hierarchically the outputFile, arch, input files, output sections and
-// symbol:
+// This file implements the -map option, which maps address ranges to their
+// respective contents, plus the input file these contents were originally from.
+// The contents (typically symbols) are listed in address order. Dead-stripped
+// contents are included as well.
//
// # Path: test
// # Arch: x86_84
// [ 0] linker synthesized
// [ 1] a.o
// # Sections:
-// # Address Size Segment Section
-// 0x1000005C0 0x0000004C __TEXT __text
+// # Address Size Segment Section
+// 0x1000005C0 0x0000004C __TEXT __text
// # Symbols:
-// # Address File Name
-// 0x1000005C0 [ 1] _main
+// # Address Size File Name
+// 0x1000005C0 0x00000001 [ 1] _main
+// # Dead Stripped Symbols:
+// # Size File Name
+// <<dead>> 0x00000001 [ 1] _foo
//
//===----------------------------------------------------------------------===//
#include "MapFile.h"
+#include "ConcatOutputSection.h"
#include "Config.h"
#include "InputFiles.h"
#include "InputSection.h"
-#include "OutputSection.h"
#include "OutputSegment.h"
#include "Symbols.h"
+#include "SyntheticSections.h"
#include "Target.h"
+#include "lld/Common/ErrorHandler.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/TimeProfiler.h"
using namespace lld;
using namespace lld::macho;
-using SymbolMapTy = DenseMap<const InputSection *, SmallVector<Defined *, 4>>;
-
-// Returns a map from sections to their symbols.
-static SymbolMapTy getSectionSyms(ArrayRef<Defined *> syms) {
- SymbolMapTy ret;
- for (Defined *dr : syms)
- ret[dr->isec].push_back(dr);
-
- // Sort symbols by address. We want to print out symbols in the order they
- // appear in the output file rather than the order they appeared in the input
- // files.
- for (auto &it : ret)
- parallelSort(
- it.second.begin(), it.second.end(), [](Defined *a, Defined *b) {
- return a->getVA() != b->getVA() ? a->getVA() < b->getVA()
- : a->getName() < b->getName();
- });
- return ret;
-}
+struct CStringInfo {
+ uint32_t fileIndex;
+ StringRef str;
+};
+
+struct MapInfo {
+ SmallVector<InputFile *> files;
+ SmallVector<Defined *> deadSymbols;
+ DenseMap<const OutputSection *,
+ SmallVector<std::pair<uint64_t /*addr*/, CStringInfo>>>
+ liveCStringsForSection;
+ SmallVector<CStringInfo> deadCStrings;
+};
+
+static MapInfo gatherMapInfo() {
+ MapInfo info;
+ for (InputFile *file : inputFiles) {
+ bool isReferencedFile = false;
+
+ if (isa<ObjFile>(file) || isa<BitcodeFile>(file)) {
+ uint32_t fileIndex = info.files.size() + 1;
-// Returns a list of all symbols that we want to print out.
-static std::vector<Defined *> getSymbols() {
- std::vector<Defined *> v;
- for (InputFile *file : inputFiles)
- if (isa<ObjFile>(file))
- for (Symbol *sym : file->symbols)
+ // Gather the dead symbols. We don't have to bother with the live ones
+ // because we will pick them up as we iterate over the OutputSections
+ // later.
+ for (Symbol *sym : file->symbols) {
if (auto *d = dyn_cast_or_null<Defined>(sym))
- if (d->isLive() && d->isec && d->getFile() == file) {
- assert(!shouldOmitFromOutput(d->isec));
- v.push_back(d);
+ // Only emit the prevailing definition of a symbol. Also, don't emit
+ // the symbol if it is part of a cstring section (we use the literal
+ // value instead, similar to ld64)
+ if (d->isec && d->getFile() == file &&
+ !isa<CStringInputSection>(d->isec)) {
+ isReferencedFile = true;
+ if (!d->isLive())
+ info.deadSymbols.push_back(d);
}
- return v;
+ }
+
+ // Gather all the cstrings (both live and dead). A CString(Output)Section
+ // doesn't provide us a way of figuring out which InputSections its
+ // cstring contents came from, so we need to build up that mapping here.
+ for (const Section *sec : file->sections) {
+ for (const Subsection &subsec : sec->subsections) {
+ if (auto isec = dyn_cast<CStringInputSection>(subsec.isec)) {
+ auto &liveCStrings = info.liveCStringsForSection[isec->parent];
+ for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+ if (piece.live)
+ liveCStrings.push_back({isec->parent->addr + piece.outSecOff,
+ {fileIndex, isec->getStringRef(i)}});
+ else
+ info.deadCStrings.push_back({fileIndex, isec->getStringRef(i)});
+ isReferencedFile = true;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ } else if (const auto *dylibFile = dyn_cast<DylibFile>(file)) {
+ isReferencedFile = dylibFile->isReferenced();
+ }
+
+ if (isReferencedFile)
+ info.files.push_back(file);
+ }
+
+ // cstrings are not stored in sorted order in their OutputSections, so we sort
+ // them here.
+ for (auto &liveCStrings : info.liveCStringsForSection)
+ parallelSort(liveCStrings.second, [](const auto &p1, const auto &p2) {
+ return p1.first < p2.first;
+ });
+ return info;
+}
+
+// For printing the contents of the __stubs and __la_symbol_ptr sections.
+void printStubsEntries(
+ raw_fd_ostream &os,
+ const DenseMap<lld::macho::InputFile *, uint32_t> &readerToFileOrdinal,
+ const OutputSection *osec, size_t entrySize) {
+ for (const Symbol *sym : in.stubs->getEntries())
+ os << format("0x%08llX\t0x%08zX\t[%3u] %s\n",
+ osec->addr + sym->stubsIndex * entrySize, entrySize,
+ readerToFileOrdinal.lookup(sym->getFile()),
+ sym->getName().str().data());
}
-// Construct a map from symbols to their stringified representations.
-// Demangling symbols (which is what toString() does) is slow, so
-// we do that in batch using parallel-for.
-static DenseMap<Symbol *, std::string>
-getSymbolStrings(ArrayRef<Defined *> syms) {
- std::vector<std::string> str(syms.size());
- parallelForEachN(0, syms.size(), [&](size_t i) {
- raw_string_ostream os(str[i]);
- os << toString(*syms[i]);
- });
-
- DenseMap<Symbol *, std::string> ret;
- for (size_t i = 0, e = syms.size(); i < e; ++i)
- ret[syms[i]] = std::move(str[i]);
- return ret;
+void printNonLazyPointerSection(raw_fd_ostream &os,
+ NonLazyPointerSectionBase *osec) {
+ // ld64 considers stubs to belong to particular files, but considers GOT
+ // entries to be linker-synthesized. Not sure why they made that decision, but
+ // I think we can follow suit unless there's demand for better symbol-to-file
+ // associations.
+ for (const Symbol *sym : osec->getEntries())
+ os << format("0x%08llX\t0x%08zX\t[ 0] non-lazy-pointer-to-local: %s\n",
+ osec->addr + sym->gotIndex * target->wordSize,
+ target->wordSize, sym->getName().str().data());
}
void macho::writeMapFile() {
return;
}
- // Dump output path.
os << format("# Path: %s\n", config->outputFile.str().c_str());
-
- // Dump output architecture.
os << format("# Arch: %s\n",
getArchitectureName(config->arch()).str().c_str());
- // Dump table of object files.
+ MapInfo info = gatherMapInfo();
+
os << "# Object files:\n";
os << format("[%3u] %s\n", 0, (const char *)"linker synthesized");
uint32_t fileIndex = 1;
DenseMap<lld::macho::InputFile *, uint32_t> readerToFileOrdinal;
- for (InputFile *file : inputFiles) {
- if (isa<ObjFile>(file)) {
- os << format("[%3u] %s\n", fileIndex, file->getName().str().c_str());
- readerToFileOrdinal[file] = fileIndex++;
- }
+ for (InputFile *file : info.files) {
+ os << format("[%3u] %s\n", fileIndex, file->getName().str().c_str());
+ readerToFileOrdinal[file] = fileIndex++;
}
- // Collect symbol info that we want to print out.
- std::vector<Defined *> syms = getSymbols();
- SymbolMapTy sectionSyms = getSectionSyms(syms);
- DenseMap<Symbol *, std::string> symStr = getSymbolStrings(syms);
-
- // Dump table of sections
os << "# Sections:\n";
os << "# Address\tSize \tSegment\tSection\n";
for (OutputSegment *seg : outputSegments)
seg->name.str().c_str(), osec->name.str().c_str());
}
- // Dump table of symbols
os << "# Symbols:\n";
- os << "# Address\t File Name\n";
- for (InputSection *isec : inputSections) {
- auto symsIt = sectionSyms.find(isec);
- assert(!shouldOmitFromOutput(isec) || (symsIt == sectionSyms.end()));
- if (symsIt == sectionSyms.end())
- continue;
- for (Symbol *sym : symsIt->second) {
- os << format("0x%08llX\t[%3u] %s\n", sym->getVA(),
- readerToFileOrdinal[sym->getFile()], symStr[sym].c_str());
+ os << "# Address\tSize \tFile Name\n";
+ for (const OutputSegment *seg : outputSegments) {
+ for (const OutputSection *osec : seg->getSections()) {
+ if (auto *concatOsec = dyn_cast<ConcatOutputSection>(osec)) {
+ for (const InputSection *isec : concatOsec->inputs) {
+ for (Defined *sym : isec->symbols)
+ os << format("0x%08llX\t0x%08llX\t[%3u] %s\n", sym->getVA(),
+ sym->size, readerToFileOrdinal[sym->getFile()],
+ sym->getName().str().data());
+ }
+ } else if (osec == in.cStringSection || osec == in.objcMethnameSection) {
+ const auto &liveCStrings = info.liveCStringsForSection.lookup(osec);
+ uint64_t lastAddr = 0; // strings will never start at address 0, so this
+ // is a sentinel value
+ for (const auto &[addr, info] : liveCStrings) {
+ uint64_t size = 0;
+ if (addr != lastAddr)
+ size = info.str.size() + 1; // include null terminator
+ lastAddr = addr;
+ os << format("0x%08llX\t0x%08llX\t[%3u] literal string: ", addr, size,
+ info.fileIndex);
+ os.write_escaped(info.str) << "\n";
+ }
+ } else if (osec == (void *)in.unwindInfo) {
+ os << format("0x%08llX\t0x%08llX\t[ 0] compact unwind info\n",
+ osec->addr, osec->getSize());
+ } else if (osec == in.stubs) {
+ printStubsEntries(os, readerToFileOrdinal, osec, target->stubSize);
+ } else if (osec == in.lazyPointers) {
+ printStubsEntries(os, readerToFileOrdinal, osec, target->wordSize);
+ } else if (osec == in.stubHelper) {
+ // yes, ld64 calls it "helper helper"...
+ os << format("0x%08llX\t0x%08llX\t[ 0] helper helper\n", osec->addr,
+ osec->getSize());
+ } else if (osec == in.got) {
+ printNonLazyPointerSection(os, in.got);
+ } else if (osec == in.tlvPointers) {
+ printNonLazyPointerSection(os, in.tlvPointers);
+ }
+ // TODO print other synthetic sections
}
}
- // TODO: when we implement -dead_strip, we should dump dead stripped symbols
+ if (config->deadStrip) {
+ os << "# Dead Stripped Symbols:\n";
+ os << "# \tSize \tFile Name\n";
+ for (Defined *sym : info.deadSymbols) {
+ assert(!sym->isLive());
+ os << format("<<dead>>\t0x%08llX\t[%3u] %s\n", sym->size,
+ readerToFileOrdinal[sym->getFile()],
+ sym->getName().str().data());
+ }
+ for (CStringInfo &cstrInfo : info.deadCStrings) {
+ os << format("<<dead>>\t0x%08zX\t[%3u] literal string: ",
+ cstrInfo.str.size() + 1, cstrInfo.fileIndex);
+ os.write_escaped(cstrInfo.str) << "\n";
+ }
+ }
}
#ifndef LLD_MACHO_MAPFILE_H
#define LLD_MACHO_MAPFILE_H
-namespace lld {
-namespace macho {
+namespace lld::macho {
void writeMapFile();
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "SymbolTable.h"
#include "Symbols.h"
#include "UnwindInfoSection.h"
-#include "mach-o/compact_unwind_encoding.h"
+
+#include "lld/Common/ErrorHandler.h"
#include "llvm/Support/TimeProfiler.h"
-namespace lld {
-namespace macho {
+#include "mach-o/compact_unwind_encoding.h"
+
+namespace lld::macho {
using namespace llvm;
using namespace llvm::MachO;
+struct WhyLiveEntry {
+ InputSection *isec;
+ // Keep track of the entry that caused us to mark `isec` as live.
+ const WhyLiveEntry *prev;
+
+ WhyLiveEntry(InputSection *isec, const WhyLiveEntry *prev)
+ : isec(isec), prev(prev) {}
+};
+
+// Type-erased interface to MarkLiveImpl. Used for adding roots to the liveness
+// graph.
+class MarkLive {
+public:
+ virtual void enqueue(InputSection *isec, uint64_t off) = 0;
+ virtual void addSym(Symbol *s) = 0;
+ virtual void markTransitively() = 0;
+ virtual ~MarkLive() = default;
+};
+
+template <bool RecordWhyLive> class MarkLiveImpl : public MarkLive {
+public:
+ // -why_live is a rarely used option, so we don't want support for that flag
+ // to slow down the main -dead_strip code path. As such, we employ templates
+ // to avoid the usage of WhyLiveEntry in the main code path. This saves us
+ // from needless allocations and pointer indirections.
+ using WorklistEntry =
+ std::conditional_t<RecordWhyLive, WhyLiveEntry, InputSection>;
+
+ void enqueue(InputSection *isec, uint64_t off) override {
+ enqueue(isec, off, nullptr);
+ }
+ void addSym(Symbol *s) override { addSym(s, nullptr); }
+ void markTransitively() override;
+
+private:
+ void enqueue(InputSection *isec, uint64_t off, const WorklistEntry *prev);
+ void addSym(Symbol *s, const WorklistEntry *prev);
+ const InputSection *getInputSection(const WorklistEntry *) const;
+ WorklistEntry *makeEntry(InputSection *, const WorklistEntry *prev) const;
+
+ // We build up a worklist of sections which have been marked as live. We
+ // only push into the worklist when we discover an unmarked section, and we
+ // mark as we push, so sections never appear twice in the list. Literal
+ // sections cannot contain references to other sections, so we only store
+ // ConcatInputSections in our worklist.
+ SmallVector<WorklistEntry *, 256> worklist;
+};
+
+template <bool RecordWhyLive>
+void MarkLiveImpl<RecordWhyLive>::enqueue(
+ InputSection *isec, uint64_t off,
+ const typename MarkLiveImpl<RecordWhyLive>::WorklistEntry *prev) {
+ if (isec->isLive(off))
+ return;
+ isec->markLive(off);
+ if (auto s = dyn_cast<ConcatInputSection>(isec)) {
+ assert(!s->isCoalescedWeak());
+ worklist.push_back(makeEntry(s, prev));
+ }
+}
+
+static void printWhyLive(const Symbol *s, const WhyLiveEntry *prev) {
+ std::string out = toString(*s) + " from " + toString(s->getFile());
+ int indent = 2;
+ for (const WhyLiveEntry *entry = prev; entry;
+ entry = entry->prev, indent += 2) {
+ const TinyPtrVector<Defined *> &symbols = entry->isec->symbols;
+ // With .subsections_with_symbols set, most isecs will have exactly one
+ // entry in their symbols vector, so we just print the first one.
+ if (!symbols.empty())
+ out += "\n" + std::string(indent, ' ') + toString(*symbols.front()) +
+ " from " + toString(symbols.front()->getFile());
+ }
+ message(out);
+}
+
+template <bool RecordWhyLive>
+void MarkLiveImpl<RecordWhyLive>::addSym(
+ Symbol *s,
+ const typename MarkLiveImpl<RecordWhyLive>::WorklistEntry *prev) {
+ if (s->used)
+ return;
+ s->used = true;
+ if constexpr (RecordWhyLive)
+ if (!config->whyLive.empty() && config->whyLive.match(s->getName()))
+ printWhyLive(s, prev);
+ if (auto *d = dyn_cast<Defined>(s)) {
+ if (d->isec)
+ enqueue(d->isec, d->value, prev);
+ if (d->unwindEntry)
+ enqueue(d->unwindEntry, 0, prev);
+ }
+}
+
+template <bool RecordWhyLive>
+const InputSection *MarkLiveImpl<RecordWhyLive>::getInputSection(
+ const MarkLiveImpl<RecordWhyLive>::WorklistEntry *entry) const {
+ if constexpr (RecordWhyLive)
+ return entry->isec;
+ else
+ return entry;
+}
+
+template <bool RecordWhyLive>
+typename MarkLiveImpl<RecordWhyLive>::WorklistEntry *
+MarkLiveImpl<RecordWhyLive>::makeEntry(
+ InputSection *isec,
+ const MarkLiveImpl<RecordWhyLive>::WorklistEntry *prev) const {
+ if constexpr (RecordWhyLive) {
+ if (!isec) {
+ assert(!prev);
+ return nullptr;
+ }
+ return make<WhyLiveEntry>(isec, prev);
+ } else {
+ return isec;
+ }
+}
+
+template <bool RecordWhyLive>
+void MarkLiveImpl<RecordWhyLive>::markTransitively() {
+ do {
+ // Mark things reachable from GC roots as live.
+ while (!worklist.empty()) {
+ WorklistEntry *entry = worklist.pop_back_val();
+ // Entries that get placed onto the worklist always contain
+ // ConcatInputSections. `WhyLiveEntry::prev` may point to entries that
+ // contain other types of InputSections (due to S_ATTR_LIVE_SUPPORT), but
+ // those entries should never be pushed onto the worklist.
+ auto *isec = cast<ConcatInputSection>(getInputSection(entry));
+ assert(isec->live && "We mark as live when pushing onto the worklist!");
+
+ // Mark all symbols listed in the relocation table for this section.
+ for (const Reloc &r : isec->relocs) {
+ if (auto *s = r.referent.dyn_cast<Symbol *>())
+ addSym(s, entry);
+ else
+ enqueue(r.referent.get<InputSection *>(), r.addend, entry);
+ }
+ for (Defined *d : getInputSection(entry)->symbols)
+ addSym(d, entry);
+ }
+
+ // S_ATTR_LIVE_SUPPORT sections are live if they point _to_ a live
+ // section. Process them in a second pass.
+ for (ConcatInputSection *isec : inputSections) {
+ // FIXME: Check if copying all S_ATTR_LIVE_SUPPORT sections into a
+ // separate vector and only walking that here is faster.
+ if (!(isec->getFlags() & S_ATTR_LIVE_SUPPORT) || isec->live)
+ continue;
+
+ for (const Reloc &r : isec->relocs) {
+ if (auto *s = r.referent.dyn_cast<Symbol *>()) {
+ if (s->isLive()) {
+ InputSection *referentIsec = nullptr;
+ if (auto *d = dyn_cast<Defined>(s))
+ referentIsec = d->isec;
+ enqueue(isec, 0, makeEntry(referentIsec, nullptr));
+ }
+ } else {
+ auto *referentIsec = r.referent.get<InputSection *>();
+ if (referentIsec->isLive(r.addend))
+ enqueue(isec, 0, makeEntry(referentIsec, nullptr));
+ }
+ }
+ }
+
+ // S_ATTR_LIVE_SUPPORT could have marked additional sections live,
+ // which in turn could mark additional S_ATTR_LIVE_SUPPORT sections live.
+ // Iterate. In practice, the second iteration won't mark additional
+ // S_ATTR_LIVE_SUPPORT sections live.
+ } while (!worklist.empty());
+}
+
// Set live bit on for each reachable chunk. Unmarked (unreachable)
// InputSections will be ignored by Writer, so they will be excluded
// from the final output.
void markLive() {
TimeTraceScope timeScope("markLive");
-
- // We build up a worklist of sections which have been marked as live. We only
- // push into the worklist when we discover an unmarked section, and we mark
- // as we push, so sections never appear twice in the list.
- // Literal sections cannot contain references to other sections, so we only
- // store ConcatInputSections in our worklist.
- SmallVector<ConcatInputSection *, 256> worklist;
-
- auto enqueue = [&](InputSection *isec, uint64_t off) {
- if (isec->isLive(off))
- return;
- isec->markLive(off);
- if (auto s = dyn_cast<ConcatInputSection>(isec)) {
- assert(!s->isCoalescedWeak());
- worklist.push_back(s);
- }
- };
-
- auto addSym = [&](Symbol *s) {
- s->used = true;
- if (auto *d = dyn_cast<Defined>(s))
- if (d->isec)
- enqueue(d->isec, d->value);
- };
-
+ MarkLive *marker;
+ if (config->whyLive.empty())
+ marker = make<MarkLiveImpl<false>>();
+ else
+ marker = make<MarkLiveImpl<true>>();
// Add GC roots.
if (config->entry)
- addSym(config->entry);
+ marker->addSym(config->entry);
for (Symbol *sym : symtab->getSymbols()) {
if (auto *defined = dyn_cast<Defined>(sym)) {
// -exported_symbol(s_list)
if (!config->exportedSymbols.empty() &&
config->exportedSymbols.match(defined->getName())) {
- // FIXME: Instead of doing this here, maybe the Driver code doing
- // the matching should add them to explicitUndefineds? Then the
- // explicitUndefineds code below would handle this automatically.
- assert(!defined->privateExtern &&
- "should have been rejected by driver");
- addSym(defined);
+ // NOTE: Even though exporting private externs is an ill-defined
+ // operation, we are purposely not checking for privateExtern in
+ // order to follow ld64's behavior of treating all exported private
+ // extern symbols as live, irrespective of whether they are autohide.
+ marker->addSym(defined);
continue;
}
// public symbols explicitly marked .no_dead_strip
if (defined->referencedDynamically || defined->noDeadStrip) {
- addSym(defined);
+ marker->addSym(defined);
continue;
}
- // FIXME: When we implement these flags, make symbols from them GC roots:
+ // FIXME: When we implement these flags, make symbols from them GC
+ // roots:
// * -reexported_symbol(s_list)
- // * -alias(-list)
+ // * -alias_list
// * -init
// In dylibs and bundles and in executables with -export_dynamic,
bool externsAreRoots =
config->outputType != MH_EXECUTE || config->exportDynamic;
if (externsAreRoots && !defined->privateExtern) {
- addSym(defined);
+ marker->addSym(defined);
continue;
}
}
}
// -u symbols
for (Symbol *sym : config->explicitUndefineds)
- if (auto *defined = dyn_cast<Defined>(sym))
- addSym(defined);
+ marker->addSym(sym);
// local symbols explicitly marked .no_dead_strip
for (const InputFile *file : inputFiles)
if (auto *objFile = dyn_cast<ObjFile>(file))
for (Symbol *sym : objFile->symbols)
if (auto *defined = dyn_cast_or_null<Defined>(sym))
if (!defined->isExternal() && defined->noDeadStrip)
- addSym(defined);
+ marker->addSym(defined);
if (auto *stubBinder =
dyn_cast_or_null<DylibSymbol>(symtab->find("dyld_stub_binder")))
- addSym(stubBinder);
+ marker->addSym(stubBinder);
for (ConcatInputSection *isec : inputSections) {
// Sections marked no_dead_strip
if (isec->getFlags() & S_ATTR_NO_DEAD_STRIP) {
- enqueue(isec, 0);
+ marker->enqueue(isec, 0);
continue;
}
// mod_init_funcs, mod_term_funcs sections
if (sectionType(isec->getFlags()) == S_MOD_INIT_FUNC_POINTERS ||
sectionType(isec->getFlags()) == S_MOD_TERM_FUNC_POINTERS) {
- enqueue(isec, 0);
+ assert(!config->emitInitOffsets ||
+ sectionType(isec->getFlags()) != S_MOD_INIT_FUNC_POINTERS);
+ marker->enqueue(isec, 0);
continue;
}
}
- // Dead strip runs before UnwindInfoSection handling so we need to keep
- // __LD,__compact_unwind alive here.
- // But that section contains absolute references to __TEXT,__text and
- // keeps most code alive due to that. So we can't just enqueue() the
- // section: We must skip the relocations for the functionAddress
- // in each CompactUnwindEntry.
- // See also scanEhFrameSection() in lld/ELF/MarkLive.cpp.
- for (ConcatInputSection *isec : in.unwindInfo->getInputs()) {
- isec->live = true;
- const int compactUnwindEntrySize =
- target->wordSize == 8 ? sizeof(CompactUnwindEntry<uint64_t>)
- : sizeof(CompactUnwindEntry<uint32_t>);
- for (const Reloc &r : isec->relocs) {
- // This is the relocation for the address of the function itself.
- // Ignore it, else these would keep everything alive.
- if (r.offset % compactUnwindEntrySize == 0)
- continue;
-
- if (auto *s = r.referent.dyn_cast<Symbol *>())
- addSym(s);
- else
- enqueue(r.referent.get<InputSection *>(), r.addend);
- }
- }
+ for (ConcatInputSection *isec : in.initOffsets->inputs())
+ marker->enqueue(isec, 0);
- do {
- // Mark things reachable from GC roots as live.
- while (!worklist.empty()) {
- ConcatInputSection *s = worklist.pop_back_val();
- assert(s->live && "We mark as live when pushing onto the worklist!");
-
- // Mark all symbols listed in the relocation table for this section.
- for (const Reloc &r : s->relocs) {
- if (auto *s = r.referent.dyn_cast<Symbol *>())
- addSym(s);
- else
- enqueue(r.referent.get<InputSection *>(), r.addend);
- }
- }
-
- // S_ATTR_LIVE_SUPPORT sections are live if they point _to_ a live section.
- // Process them in a second pass.
- for (ConcatInputSection *isec : inputSections) {
- // FIXME: Check if copying all S_ATTR_LIVE_SUPPORT sections into a
- // separate vector and only walking that here is faster.
- if (!(isec->getFlags() & S_ATTR_LIVE_SUPPORT) || isec->live)
- continue;
-
- for (const Reloc &r : isec->relocs) {
- bool referentLive;
- if (auto *s = r.referent.dyn_cast<Symbol *>())
- referentLive = s->isLive();
- else
- referentLive = r.referent.get<InputSection *>()->isLive(r.addend);
- if (referentLive)
- enqueue(isec, 0);
- }
- }
-
- // S_ATTR_LIVE_SUPPORT could have marked additional sections live,
- // which in turn could mark additional S_ATTR_LIVE_SUPPORT sections live.
- // Iterate. In practice, the second iteration won't mark additional
- // S_ATTR_LIVE_SUPPORT sections live.
- } while (!worklist.empty());
+ marker->markTransitively();
}
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#ifndef LLD_MACHO_MARKLIVE_H
#define LLD_MACHO_MARKLIVE_H
-namespace lld {
-namespace macho {
+namespace lld::macho {
void markLive();
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif // LLD_MACHO_MARKLIVE_H
#include "OutputSegment.h"
#include "Target.h"
+#include "lld/Common/ErrorHandler.h"
#include "llvm/BinaryFormat/MachO.h"
+#include "llvm/Bitcode/BitcodeReader.h"
using namespace llvm;
using namespace llvm::MachO;
using namespace lld;
using namespace lld::macho;
-template <class LP> static bool hasObjCSection(MemoryBufferRef mb) {
- using Section = typename LP::section;
+template <class LP> static bool objectHasObjCSection(MemoryBufferRef mb) {
+ using SectionHeader = typename LP::section;
auto *hdr =
reinterpret_cast<const typename LP::mach_header *>(mb.getBufferStart());
if (const auto *c =
findCommand<typename LP::segment_command>(hdr, LP::segmentLCType)) {
- auto sectionHeaders =
- ArrayRef<Section>{reinterpret_cast<const Section *>(c + 1), c->nsects};
- for (const Section &sec : sectionHeaders) {
- StringRef sectname(sec.sectname,
- strnlen(sec.sectname, sizeof(sec.sectname)));
- StringRef segname(sec.segname, strnlen(sec.segname, sizeof(sec.segname)));
+ auto sectionHeaders = ArrayRef<SectionHeader>{
+ reinterpret_cast<const SectionHeader *>(c + 1), c->nsects};
+ for (const SectionHeader &secHead : sectionHeaders) {
+ StringRef sectname(secHead.sectname,
+ strnlen(secHead.sectname, sizeof(secHead.sectname)));
+ StringRef segname(secHead.segname,
+ strnlen(secHead.segname, sizeof(secHead.segname)));
if ((segname == segment_names::data &&
sectname == section_names::objcCatList) ||
(segname == segment_names::text &&
- sectname == section_names::swift)) {
+ sectname.startswith(section_names::swift))) {
return true;
}
}
return false;
}
-bool macho::hasObjCSection(MemoryBufferRef mb) {
+static bool objectHasObjCSection(MemoryBufferRef mb) {
if (target->wordSize == 8)
- return ::hasObjCSection<LP64>(mb);
+ return ::objectHasObjCSection<LP64>(mb);
else
- return ::hasObjCSection<ILP32>(mb);
+ return ::objectHasObjCSection<ILP32>(mb);
+}
+
+bool macho::hasObjCSection(MemoryBufferRef mb) {
+ switch (identify_magic(mb.getBuffer())) {
+ case file_magic::macho_object:
+ return objectHasObjCSection(mb);
+ case file_magic::bitcode:
+ return check(isBitcodeContainingObjCCategory(mb));
+ default:
+ return false;
+ }
}
#include "llvm/Support/MemoryBuffer.h"
-namespace lld {
-namespace macho {
+namespace lld::macho {
namespace objc {
bool hasObjCSection(llvm::MemoryBufferRef);
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
def threads_eq : Joined<["--"], "threads=">,
HelpText<"Number of threads. '1' disables multi-threading. By default all available hardware threads are used">,
Group<grp_lld>;
+def thinlto_emit_imports_files: Flag<["--"], "thinlto-emit-imports-files">,
+ Group<grp_lld>;
+def thinlto_emit_index_files: Flag<["--"], "thinlto-emit-index-files">,
+ Group<grp_lld>;
+def thinlto_index_only: Flag<["--"], "thinlto-index-only">,
+ Group<grp_lld>;
+def thinlto_index_only_eq: Joined<["--"], "thinlto-index-only=">,
+ Group<grp_lld>;
def thinlto_jobs_eq : Joined<["--"], "thinlto-jobs=">,
HelpText<"Number of ThinLTO jobs. Default to --threads=">,
Group<grp_lld>;
+def thinlto_object_suffix_replace_eq:
+ Joined<["--"], "thinlto-object-suffix-replace=">,
+ Group<grp_lld>;
+def thinlto_prefix_replace_eq: Joined<["--"], "thinlto-prefix-replace=">,
+ Group<grp_lld>;
def reproduce: Separate<["--"], "reproduce">,
Group<grp_lld>;
def reproduce_eq: Joined<["--"], "reproduce=">,
def version: Flag<["--"], "version">,
HelpText<"Display the version number and exit">,
Group<grp_lld>;
-def lto_legacy_pass_manager: Flag<["--"], "lto-legacy-pass-manager">,
- HelpText<"Use the legacy pass manager in LLVM">,
- Group<grp_lld>;
def no_lto_legacy_pass_manager : Flag<["--"], "no-lto-legacy-pass-manager">,
HelpText<"Use the new pass manager in LLVM">,
Group<grp_lld>;
-def time_trace: Flag<["--"], "time-trace">, HelpText<"Record time trace">,
+def time_trace_eq: Joined<["--"], "time-trace=">,
+ HelpText<"Record time trace to <file>">,
+ MetaVarName<"<file>">,
+ Group<grp_lld>;
+def : Flag<["--"], "time-trace">,
+ Alias<time_trace_eq>,
+ HelpText<"Record time trace to file next to output">,
Group<grp_lld>;
def time_trace_granularity_eq: Joined<["--"], "time-trace-granularity=">,
HelpText<"Minimum time granularity (in microseconds) traced by time profiler">,
Group<grp_lld>;
-def time_trace_file_eq: Joined<["--"], "time-trace-file=">,
- HelpText<"Specify time trace output file">,
+def deduplicate_strings: Flag<["--"], "deduplicate-strings">,
+ HelpText<"Enable string deduplication">,
+ Group<grp_lld>;
+def no_deduplicate_strings: Flag<["--"], "no-deduplicate-strings">,
+ HelpText<"Disable string deduplication. This helps uncover cases of comparing string addresses instead of equality and might have a link time performance benefit.">,
Group<grp_lld>;
-def deduplicate_literals: Flag<["--"], "deduplicate-literals">,
- HelpText<"Enable literal deduplication. This is implied by --icf={safe,all}">,
+def dead_strip_duplicates: Flag<["--"], "dead-strip-duplicates">,
+ HelpText<"Do not error on duplicate symbols that will be dead stripped.">,
Group<grp_lld>;
def print_dylib_search: Flag<["--"], "print-dylib-search">,
HelpText<"Print which paths lld searched when trying to find dylibs">,
HelpText<"Set optimization level for LTO (default: 2)">,
MetaVarName<"<opt-level>">,
Group<grp_lld>;
-def thinlto_cache_policy: Joined<["--"], "thinlto-cache-policy=">,
+def thinlto_cache_policy_eq: Joined<["--"], "thinlto-cache-policy=">,
HelpText<"Pruning policy for the ThinLTO cache">,
Group<grp_lld>;
def O : JoinedOrSeparate<["-"], "O">,
HelpText<"Optimize output file size">;
+def start_lib: Flag<["--"], "start-lib">,
+ HelpText<"Start a grouping of objects that should be treated as if they were together in an archive">;
+def end_lib: Flag<["--"], "end-lib">,
+ HelpText<"End a grouping of objects that should be treated as if they were together in an archive">;
+def no_warn_dylib_install_name: Flag<["--"], "no-warn-dylib-install-name">,
+ HelpText<"Do not warn on -install_name if -dylib is not passed (default)">,
+ Group<grp_lld>;
+def warn_dylib_install_name: Flag<["--"], "warn-dylib-install-name">,
+ HelpText<"Warn on -install_name if -dylib is not passed">,
+ Group<grp_lld>;
+def call_graph_profile_sort: Flag<["--"], "call-graph-profile-sort">,
+ HelpText<"Reorder sections with call graph profile (default)">,
+ Group<grp_lld>;
+def no_call_graph_profile_sort : Flag<["--"], "no-call-graph-profile-sort">,
+ HelpText<"Do not reorder sections with call graph profile">,
+ Group<grp_lld>;
+def print_symbol_order_eq: Joined<["--"], "print-symbol-order=">,
+ HelpText<"Print a symbol order specified by --call-graph-profile-sort into the specified file">,
+ Group<grp_lld>;
+def ignore_auto_link_option : Separate<["--"], "ignore-auto-link-option">,
+ Group<grp_lld>;
+def ignore_auto_link_option_eq : Joined<["--"], "ignore-auto-link-option=">,
+ Alias<!cast<Separate>(ignore_auto_link_option)>,
+ HelpText<"Ignore a single auto-linked library or framework. Useful to ignore invalid options that ld64 ignores">,
+ Group<grp_lld>;
+def strict_auto_link : Flag<["--"], "strict-auto-link">,
+ HelpText<"Always warn for missing frameworks or libraries if they are loaded via LC_LINKER_OPTIONS">,
+ Group<grp_lld>;
// This is a complete Options.td compiled from Apple's ld(1) manpage
// dated 2018-03-07 and cross checked with ld64 source code in repo
def all_load : Flag<["-"], "all_load">,
HelpText<"Load all members of all static archive libraries">,
Group<grp_libs>;
+def noall_load : Flag<["-"], "noall_load">,
+ HelpText<"Don't load all static members from archives, this is the default, this negates -all_load">,
+ Group<grp_libs>;
def ObjC : Flag<["-"], "ObjC">,
HelpText<"Load all members of static archives that are an Objective-C class or category.">,
Group<grp_libs>;
def force_load_swift_libs : Flag<["-"], "force_load_swift_libs">,
HelpText<"Apply -force_load to libraries listed in LC_LINKER_OPTIONS whose names start with 'swift'">,
Group<grp_libs>;
+def load_hidden : Separate<["-"], "load_hidden">,
+ MetaVarName<"<path>">,
+ HelpText<"Load all symbols from static library with hidden visibility">,
+ Group<grp_libs>;
+def hidden_l : Joined<["-"], "hidden-l">,
+ MetaVarName<"<name>">,
+ HelpText<"Like -l<name>, but load all symbols with hidden visibility">,
+ Group<grp_libs>;
def grp_content : OptionGroup<"content">, HelpText<"ADDITIONAL CONTENT">;
Alias<sectcreate>,
HelpText<"Alias for -sectcreate">,
Group<grp_content>;
+def add_empty_section : MultiArg<["-"], "add_empty_section", 2>,
+ MetaVarName<"<segment> <section>">,
+ HelpText<"Create an empty <section> in <segment>">,
+ Group<grp_content>;
def filelist : Separate<["-"], "filelist">,
MetaVarName<"<file>">,
HelpText<"Read names of files to link from <file>">,
Flags<[HelpHidden]>,
Group<grp_opts>;
def no_deduplicate : Flag<["-"], "no_deduplicate">,
- HelpText<"Disable code deduplicaiton (synonym for `--icf=none')">,
+ HelpText<"Disable code deduplication (synonym for `--icf=none')">,
+ Alias<icf_eq>, AliasArgs<["none"]>,
Group<grp_opts>;
def grp_version : OptionGroup<"version">, HelpText<"VERSION TARGETING">;
def pagezero_size : Separate<["-"], "pagezero_size">,
MetaVarName<"<size>">,
HelpText<"Size of unreadable segment at address zero is hex <size> (default is 4KB on 32-bit and 4GB on 64-bit)">,
- Flags<[HelpHidden]>,
Group<grp_main>;
def stack_size : Separate<["-"], "stack_size">,
MetaVarName<"<size>">,
MetaVarName<"<file>">,
HelpText<"Symbols specified in <file> remain global, while others become private externs">,
Group<grp_resolve>;
+def no_exported_symbols : Flag<["-"], "no_exported_symbols">,
+ HelpText<"Don't export any symbols from the binary, useful for main executables that don't have plugins">,
+ Group<grp_resolve>;
def unexported_symbol : Separate<["-"], "unexported_symbol">,
MetaVarName<"<symbol>">,
HelpText<"Global <symbol> becomes private extern">,
def alias : MultiArg<["-"], "alias", 2>,
MetaVarName<"<symbol_name> <alternate_name>">,
HelpText<"Create a symbol alias with default global visibility">,
- Flags<[HelpHidden]>,
Group<grp_resolve>;
def alias_list : Separate<["-"], "alias_list">,
MetaVarName<"<file>">,
def why_live : Separate<["-"], "why_live">,
MetaVarName<"<symbol>">,
HelpText<"Log a chain of references to <symbol>, for use with -dead_strip">,
- Flags<[HelpHidden]>,
Group<grp_introspect>;
def print_statistics : Flag<["-"], "print_statistics">,
HelpText<"Log the linker's memory and CPU usage">,
def S : Flag<["-"], "S">,
HelpText<"Strip debug information (STABS or DWARF) from the output">,
- Flags<[HelpHidden]>,
Group<grp_symtab>;
def x : Flag<["-"], "x">,
HelpText<"Exclude non-global symbols from the output symbol table">,
- Flags<[HelpHidden]>,
Group<grp_symtab>;
def non_global_symbols_strip_list : Separate<["-"], "non_global_symbols_strip_list">,
MetaVarName<"<path>">,
HelpText<"Specify in <path> the non-global symbols that should be removed from the output symbol table">,
- Flags<[HelpHidden]>,
Group<grp_symtab>;
def non_global_symbols_no_strip_list : Separate<["-"], "non_global_symbols_no_strip_list">,
MetaVarName<"<path>">,
HelpText<"Specify in <path> the non-global symbols that should remain in the output symbol table">,
- Flags<[HelpHidden]>,
Group<grp_symtab>;
def oso_prefix : Separate<["-"], "oso_prefix">,
MetaVarName<"<path>">,
HelpText<"Remove the prefix <path> from OSO symbols in the debug map">,
- Flags<[HelpHidden]>,
Group<grp_symtab>;
def add_ast_path : Separate<["-"], "add_ast_path">,
MetaVarName<"<path>">,
Group<grp_rare>;
def arch_errors_fatal : Flag<["-"], "arch_errors_fatal">,
HelpText<"Escalate to errors any warnings about inputs whose architecture does not match the -arch option">,
- Flags<[HelpHidden]>,
Group<grp_rare>;
def e : Separate<["-"], "e">,
MetaVarName<"<symbol>">,
Group<grp_rare>;
def w : Flag<["-"], "w">,
HelpText<"Suppress all warnings">,
- Flags<[HelpHidden]>,
Group<grp_rare>;
def final_output : Separate<["-"], "final_output">,
MetaVarName<"<name>">,
def mcpu : Separate<["-"], "mcpu">,
HelpText<"Processor family target for LTO code generation">,
Group<grp_rare>;
+def no_dtrace_dof : Flag<["-"], "no_dtrace_dof">,
+ HelpText<"Disable dtrace-dof processing (default).">,
+ Group<grp_rare>;
+def objc_stubs_fast : Flag<["-"], "objc_stubs_fast">,
+ HelpText<"Produce larger stubs for Objective-C method calls with fewer jumps (default).">,
+ Group<grp_rare>;
+def objc_stubs_small : Flag<["-"], "objc_stubs_small">,
+ HelpText<"Produce smaller stubs for Objective-C method calls with more jumps.">,
+ Group<grp_rare>;
+def dyld_env : Separate<["-"], "dyld_env">,
+ MetaVarName<"<dyld_env_var>">,
+ HelpText<"Specifies a LC_DYLD_ENVIRONMENT variable value pair.">,
+ Group<grp_rare>;
+def ignore_auto_link : Flag<["-"], "ignore_auto_link">,
+ HelpText<"Ignore LC_LINKER_OPTIONs">,
+ Group<grp_rare>;
def grp_deprecated : OptionGroup<"deprecated">, HelpText<"DEPRECATED">;
HelpText<"Unnecessary option: initialization and termination are roots of the dead strip graph, so never dead stripped">,
Flags<[HelpHidden]>,
Group<grp_deprecated>;
-def noall_load : Flag<["-"], "noall_load">,
- HelpText<"Unnecessary option: this is already the default">,
- Flags<[HelpHidden]>,
- Group<grp_deprecated>;
def grp_obsolete : OptionGroup<"obsolete">, HelpText<"OBSOLETE">;
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
-def bitcode_process_mode : Flag<["-"], "bitcode_process_mode">,
+def bitcode_process_mode : Separate<["-"], "bitcode_process_mode">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
Group<grp_undocumented>;
def demangle : Flag<["-"], "demangle">,
HelpText<"Demangle symbol names in diagnostics">;
-def dyld_env : Flag<["-"], "dyld_env">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
- Group<grp_undocumented>;
def encryptable : Flag<["-"], "encryptable">,
HelpText<"Generate the LC_ENCRYPTION_INFO load command">,
Group<grp_undocumented>;
Flags<[HelpHidden]>,
Group<grp_undocumented>;
def fixup_chains : Flag<["-"], "fixup_chains">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
+ HelpText<"Emit chained fixups">,
+ Group<grp_undocumented>;
+def no_fixup_chains : Flag<["-"], "no_fixup_chains">,
+ HelpText<"Emit fixup information as classic dyld opcodes">,
Group<grp_undocumented>;
def fixup_chains_section : Flag<["-"], "fixup_chains_section">,
HelpText<"This option is undocumented in ld64">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
-def force_symbols_not_weak_list : Flag<["-"], "force_symbols_not_weak_list">,
+def force_symbols_not_weak_list : Separate<["-"], "force_symbols_not_weak_list">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
-def force_symbols_weak_list : Flag<["-"], "force_symbols_weak_list">,
+def force_symbols_weak_list : Separate<["-"], "force_symbols_weak_list">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
-def ignore_auto_link : Flag<["-"], "ignore_auto_link">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
- Group<grp_undocumented>;
def ignore_optimization_hints : Flag<["-"], "ignore_optimization_hints">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
+ HelpText<"Ignore Linker Optimization Hints">,
Group<grp_undocumented>;
def init_offsets : Flag<["-"], "init_offsets">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
+ HelpText<"Store __TEXT segment offsets of static initializers">,
Group<grp_undocumented>;
def keep_dwarf_unwind : Flag<["-"], "keep_dwarf_unwind">,
HelpText<"This option is undocumented in ld64">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
-def no_dtrace_dof : Flag<["-"], "no_dtrace_dof">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
- Group<grp_undocumented>;
def no_new_main : Flag<["-"], "no_new_main">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
-def objc_abi_version : Separate<["-"], "objc_abi_version">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
- Group<grp_undocumented>;
def pause : Flag<["-"], "pause">,
HelpText<"This option is undocumented in ld64">,
Flags<[HelpHidden]>,
HelpText<"This option is ignored in ld64">,
Flags<[HelpHidden]>,
Group<grp_ignored>;
+
+def grp_ignored_silently : OptionGroup<"ignored_silently">, HelpText<"IGNORED SILENTLY">;
+
+def objc_abi_version : Separate<["-"], "objc_abi_version">,
+ HelpText<"This option only applies to i386 in ld64">,
+ Flags<[HelpHidden]>,
+ Group<grp_ignored_silently>;
using namespace lld;
using namespace lld::macho;
-uint64_t OutputSection::getSegmentOffset() const {
- return addr - parent->addr;
-}
+uint64_t OutputSection::getSegmentOffset() const { return addr - parent->addr; }
void OutputSection::assignAddressesToStartEndSymbols() {
for (Defined *d : sectionStartSymbols)
#include <limits>
-namespace lld {
-namespace macho {
+namespace lld::macho {
class Defined;
class InputSection;
// Unneeded sections are omitted entirely (header and body).
virtual bool isNeeded() const { return true; }
- virtual void finalize() {
- // TODO investigate refactoring synthetic section finalization logic into
- // overrides of this function.
- }
+ // The implementations of this method can assume that it is only called right
+ // before addresses get assigned to this particular OutputSection. In
+ // particular, this means that it gets called only after addresses have been
+ // assigned to output sections that occur earlier in the output binary.
+ // Naturally, this means different sections' finalize() methods cannot execute
+ // concurrently with each other. As such, avoid using this method for
+ // operations that do not require this strict sequential guarantee.
+ //
+ // Operations that need to occur late in the linking process, but which do not
+ // need the sequential guarantee, should be named `finalizeContents()`. See
+ // e.g. LinkEditSection::finalizeContents() and
+ // CStringSection::finalizeContents().
+ virtual void finalize() {}
virtual void writeTo(uint8_t *buf) const = 0;
+ // Handle section$start$ and section$end$ symbols.
void assignAddressesToStartEndSymbols();
StringRef name;
Kind sectionKind;
};
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
return initProt(name);
}
+static uint32_t flags(StringRef name) {
+ // If we ever implement shared cache output support, SG_READ_ONLY should not
+ // be used for dylibs that can be placed in it.
+ return name == segment_names::dataConst ? (uint32_t)SG_READ_ONLY : 0;
+}
+
size_t OutputSegment::numNonHiddenSections() const {
size_t count = 0;
for (const OutputSection *osec : sections)
// Sections are uniquely identified by their segment + section name.
if (segname == segment_names::text) {
return StringSwitch<int>(osec->name)
- .Case(section_names::header, -4)
- .Case(section_names::text, -3)
- .Case(section_names::stubs, -2)
- .Case(section_names::stubHelper, -1)
+ .Case(section_names::header, -6)
+ .Case(section_names::text, -5)
+ .Case(section_names::stubs, -4)
+ .Case(section_names::stubHelper, -3)
+ .Case(section_names::objcStubs, -2)
+ .Case(section_names::initOffsets, -1)
.Case(section_names::unwindInfo, std::numeric_limits<int>::max() - 1)
.Case(section_names::ehFrame, std::numeric_limits<int>::max())
.Default(osec->inputOrder);
}
} else if (segname == segment_names::linkEdit) {
return StringSwitch<int>(osec->name)
+ .Case(section_names::chainFixups, -11)
.Case(section_names::rebase, -10)
.Case(section_names::binding, -9)
.Case(section_names::weakBinding, -8)
static DenseMap<StringRef, OutputSegment *> nameToOutputSegment;
std::vector<OutputSegment *> macho::outputSegments;
+void macho::resetOutputSegments() {
+ outputSegments.clear();
+ nameToOutputSegment.clear();
+}
+
static StringRef maybeRenameSegment(StringRef name) {
auto newName = config->segmentRenameMap.find(name);
if (newName != config->segmentRenameMap.end())
segRef->name = name;
segRef->maxProt = maxProt(name);
segRef->initProt = initProt(name);
+ segRef->flags = flags(name);
outputSegments.push_back(segRef);
return segRef;
#include <limits>
#include <vector>
-namespace lld {
-namespace macho {
+namespace lld::macho {
namespace segment_names {
StringRef name;
uint32_t maxProt = 0;
uint32_t initProt = 0;
+ uint32_t flags = 0;
uint8_t index;
llvm::TinyPtrVector<Defined *> segmentStartSymbols;
extern std::vector<OutputSegment *> outputSegments;
void sortOutputSegments();
+void resetOutputSegments();
OutputSegment *getOrCreateOutputSegment(StringRef name);
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
//===----------------------------------------------------------------------===//
#include "Relocations.h"
+#include "ConcatOutputSection.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
using namespace lld;
using namespace lld::macho;
+static_assert(sizeof(void *) != 8 || sizeof(Reloc) == 24,
+ "Try to minimize Reloc's size; we create many instances");
+
bool macho::validateSymbolRelocation(const Symbol *sym,
const InputSection *isec, const Reloc &r) {
const RelocAttrs &relocAttrs = target->getRelocAttrs(r.type);
bool valid = true;
- auto message = [relocAttrs, sym, isec, &valid](const Twine &diagnostic) {
+ auto message = [&](const Twine &diagnostic) {
valid = false;
- return (relocAttrs.name + " relocation " + diagnostic + " for `" +
- sym->getName() + "' in " + toString(isec))
+ return (isec->getLocation(r.offset) + ": " + relocAttrs.name +
+ " relocation " + diagnostic)
.str();
};
if (relocAttrs.hasAttr(RelocAttrBits::TLV) != sym->isTlv())
- error(message(Twine("requires that variable ") +
+ error(message(Twine("requires that symbol ") + sym->getName() + " " +
(sym->isTlv() ? "not " : "") + "be thread-local"));
return valid;
}
-void macho::reportRangeError(const Reloc &r, const Twine &v, uint8_t bits,
- int64_t min, uint64_t max) {
+// Given an offset in the output buffer, figure out which ConcatInputSection (if
+// any) maps to it. At the same time, update the offset such that it is relative
+// to the InputSection rather than to the output buffer.
+//
+// Obtaining the InputSection allows us to have better error diagnostics.
+// However, many of our relocation-handling methods do not take the InputSection
+// as a parameter. Since we are already passing the buffer offsets to our Target
+// methods, this function allows us to emit better errors without threading an
+// additional InputSection argument through the call stack.
+//
+// This is implemented as a slow linear search through OutputSegments,
+// OutputSections, and finally the InputSections themselves. However, this
+// function should be called only on error paths, so some overhead is fine.
+InputSection *macho::offsetToInputSection(uint64_t *off) {
+ for (OutputSegment *seg : outputSegments) {
+ if (*off < seg->fileOff || *off >= seg->fileOff + seg->fileSize)
+ continue;
+
+ const std::vector<OutputSection *> §ions = seg->getSections();
+ size_t osecIdx = 0;
+ for (; osecIdx < sections.size(); ++osecIdx)
+ if (*off < sections[osecIdx]->fileOff)
+ break;
+ assert(osecIdx > 0);
+ // We should be only calling this function on offsets that belong to
+ // ConcatOutputSections.
+ auto *osec = cast<ConcatOutputSection>(sections[osecIdx - 1]);
+ *off -= osec->fileOff;
+
+ size_t isecIdx = 0;
+ for (; isecIdx < osec->inputs.size(); ++isecIdx) {
+ const ConcatInputSection *isec = osec->inputs[isecIdx];
+ if (*off < isec->outSecOff)
+ break;
+ }
+ assert(isecIdx > 0);
+ ConcatInputSection *isec = osec->inputs[isecIdx - 1];
+ *off -= isec->outSecOff;
+ return isec;
+ }
+ return nullptr;
+}
+
+void macho::reportRangeError(void *loc, const Reloc &r, const Twine &v,
+ uint8_t bits, int64_t min, uint64_t max) {
std::string hint;
+ uint64_t off = reinterpret_cast<const uint8_t *>(loc) - in.bufferStart;
+ const InputSection *isec = offsetToInputSection(&off);
+ std::string locStr = isec ? isec->getLocation(off) : "(invalid location)";
if (auto *sym = r.referent.dyn_cast<Symbol *>())
hint = "; references " + toString(*sym);
- // TODO: get location of reloc using something like LLD-ELF's getErrorPlace()
- error("relocation " + target->getRelocAttrs(r.type).name +
+ error(locStr + ": relocation " + target->getRelocAttrs(r.type).name +
" is out of range: " + v + " is not in [" + Twine(min) + ", " +
Twine(max) + "]" + hint);
}
-void macho::reportRangeError(SymbolDiagnostic d, const Twine &v, uint8_t bits,
- int64_t min, uint64_t max) {
+void macho::reportRangeError(void *loc, SymbolDiagnostic d, const Twine &v,
+ uint8_t bits, int64_t min, uint64_t max) {
+ // FIXME: should we use `loc` somehow to provide a better error message?
std::string hint;
if (d.symbol)
hint = "; references " + toString(*d.symbol);
#include <cstddef>
#include <cstdint>
-namespace lld {
-namespace macho {
+namespace lld::macho {
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
class Symbol;
uint8_t length = 0;
// The offset from the start of the subsection that this relocation belongs
// to.
- uint64_t offset = 0;
+ uint32_t offset = 0;
// Adding this offset to the address of the referent symbol or subsection
// gives the destination that this relocation refers to.
int64_t addend = 0;
llvm::PointerUnion<Symbol *, InputSection *> referent = nullptr;
+
+ Reloc() = default;
+
+ Reloc(uint8_t type, bool pcrel, uint8_t length, uint32_t offset,
+ int64_t addend, llvm::PointerUnion<Symbol *, InputSection *> referent)
+ : type(type), pcrel(pcrel), length(length), offset(offset),
+ addend(addend), referent(referent) {}
};
bool validateSymbolRelocation(const Symbol *, const InputSection *,
* v: The value the relocation is attempting to encode
* bits: The number of bits actually available to encode this relocation
*/
-void reportRangeError(const Reloc &, const llvm::Twine &v, uint8_t bits,
- int64_t min, uint64_t max);
+void reportRangeError(void *loc, const Reloc &, const llvm::Twine &v,
+ uint8_t bits, int64_t min, uint64_t max);
struct SymbolDiagnostic {
const Symbol *symbol;
llvm::StringRef reason;
};
-void reportRangeError(SymbolDiagnostic, const llvm::Twine &v, uint8_t bits,
- int64_t min, uint64_t max);
+void reportRangeError(void *loc, SymbolDiagnostic, const llvm::Twine &v,
+ uint8_t bits, int64_t min, uint64_t max);
template <typename Diagnostic>
-inline void checkInt(Diagnostic d, int64_t v, int bits) {
+inline void checkInt(void *loc, Diagnostic d, int64_t v, int bits) {
if (v != llvm::SignExtend64(v, bits))
- reportRangeError(d, llvm::Twine(v), bits, llvm::minIntN(bits),
+ reportRangeError(loc, d, llvm::Twine(v), bits, llvm::minIntN(bits),
llvm::maxIntN(bits));
}
template <typename Diagnostic>
-inline void checkUInt(Diagnostic d, uint64_t v, int bits) {
+inline void checkUInt(void *loc, Diagnostic d, uint64_t v, int bits) {
if ((v >> bits) != 0)
- reportRangeError(d, llvm::Twine(v), bits, 0, llvm::maxUIntN(bits));
+ reportRangeError(loc, d, llvm::Twine(v), bits, 0, llvm::maxUIntN(bits));
}
inline void writeAddress(uint8_t *loc, uint64_t addr, uint8_t length) {
}
}
+InputSection *offsetToInputSection(uint64_t *);
+
extern const RelocAttrs invalidRelocAttrs;
-} // namespace macho
-} // namespace lld
+} // namespace lld::Macho
#endif
--- /dev/null
+//===- SectionPriorities.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
+//
+//===----------------------------------------------------------------------===//
+///
+/// This is based on the ELF port, see ELF/CallGraphSort.cpp for the details
+/// about the algorithm.
+///
+//===----------------------------------------------------------------------===//
+
+#include "SectionPriorities.h"
+#include "Config.h"
+#include "InputFiles.h"
+#include "Symbols.h"
+#include "Target.h"
+
+#include "lld/Common/Args.h"
+#include "lld/Common/CommonLinkerContext.h"
+#include "lld/Common/ErrorHandler.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/MapVector.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/TimeProfiler.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <numeric>
+
+using namespace llvm;
+using namespace llvm::MachO;
+using namespace llvm::sys;
+using namespace lld;
+using namespace lld::macho;
+
+PriorityBuilder macho::priorityBuilder;
+
+namespace {
+
+size_t highestAvailablePriority = std::numeric_limits<size_t>::max();
+
+struct Edge {
+ int from;
+ uint64_t weight;
+};
+
+struct Cluster {
+ Cluster(int sec, size_t s) : next(sec), prev(sec), size(s) {}
+
+ double getDensity() const {
+ if (size == 0)
+ return 0;
+ return double(weight) / double(size);
+ }
+
+ int next;
+ int prev;
+ uint64_t size;
+ uint64_t weight = 0;
+ uint64_t initialWeight = 0;
+ Edge bestPred = {-1, 0};
+};
+
+class CallGraphSort {
+public:
+ CallGraphSort(const MapVector<SectionPair, uint64_t> &profile);
+
+ DenseMap<const InputSection *, size_t> run();
+
+private:
+ std::vector<Cluster> clusters;
+ std::vector<const InputSection *> sections;
+};
+// Maximum amount the combined cluster density can be worse than the original
+// cluster to consider merging.
+constexpr int MAX_DENSITY_DEGRADATION = 8;
+} // end anonymous namespace
+
+// Take the edge list in callGraphProfile, resolve symbol names to Symbols, and
+// generate a graph between InputSections with the provided weights.
+CallGraphSort::CallGraphSort(const MapVector<SectionPair, uint64_t> &profile) {
+ DenseMap<const InputSection *, int> secToCluster;
+
+ auto getOrCreateCluster = [&](const InputSection *isec) -> int {
+ auto res = secToCluster.try_emplace(isec, clusters.size());
+ if (res.second) {
+ sections.push_back(isec);
+ clusters.emplace_back(clusters.size(), isec->getSize());
+ }
+ return res.first->second;
+ };
+
+ // Create the graph
+ for (const std::pair<SectionPair, uint64_t> &c : profile) {
+ const auto fromSec = c.first.first->canonical();
+ const auto toSec = c.first.second->canonical();
+ uint64_t weight = c.second;
+ // Ignore edges between input sections belonging to different output
+ // sections. This is done because otherwise we would end up with clusters
+ // containing input sections that can't actually be placed adjacently in the
+ // output. This messes with the cluster size and density calculations. We
+ // would also end up moving input sections in other output sections without
+ // moving them closer to what calls them.
+ if (fromSec->parent != toSec->parent)
+ continue;
+
+ int from = getOrCreateCluster(fromSec);
+ int to = getOrCreateCluster(toSec);
+
+ clusters[to].weight += weight;
+
+ if (from == to)
+ continue;
+
+ // Remember the best edge.
+ Cluster &toC = clusters[to];
+ if (toC.bestPred.from == -1 || toC.bestPred.weight < weight) {
+ toC.bestPred.from = from;
+ toC.bestPred.weight = weight;
+ }
+ }
+ for (Cluster &c : clusters)
+ c.initialWeight = c.weight;
+}
+
+// It's bad to merge clusters which would degrade the density too much.
+static bool isNewDensityBad(Cluster &a, Cluster &b) {
+ double newDensity = double(a.weight + b.weight) / double(a.size + b.size);
+ return newDensity < a.getDensity() / MAX_DENSITY_DEGRADATION;
+}
+
+// Find the leader of V's belonged cluster (represented as an equivalence
+// class). We apply union-find path-halving technique (simple to implement) in
+// the meantime as it decreases depths and the time complexity.
+static int getLeader(std::vector<int> &leaders, int v) {
+ while (leaders[v] != v) {
+ leaders[v] = leaders[leaders[v]];
+ v = leaders[v];
+ }
+ return v;
+}
+
+static void mergeClusters(std::vector<Cluster> &cs, Cluster &into, int intoIdx,
+ Cluster &from, int fromIdx) {
+ int tail1 = into.prev, tail2 = from.prev;
+ into.prev = tail2;
+ cs[tail2].next = intoIdx;
+ from.prev = tail1;
+ cs[tail1].next = fromIdx;
+ into.size += from.size;
+ into.weight += from.weight;
+ from.size = 0;
+ from.weight = 0;
+}
+
+// Group InputSections into clusters using the Call-Chain Clustering heuristic
+// then sort the clusters by density.
+DenseMap<const InputSection *, size_t> CallGraphSort::run() {
+ const uint64_t maxClusterSize = target->getPageSize();
+
+ // Cluster indices sorted by density.
+ std::vector<int> sorted(clusters.size());
+ // For union-find.
+ std::vector<int> leaders(clusters.size());
+
+ std::iota(leaders.begin(), leaders.end(), 0);
+ std::iota(sorted.begin(), sorted.end(), 0);
+
+ llvm::stable_sort(sorted, [&](int a, int b) {
+ return clusters[a].getDensity() > clusters[b].getDensity();
+ });
+
+ for (int l : sorted) {
+ // The cluster index is the same as the index of its leader here because
+ // clusters[L] has not been merged into another cluster yet.
+ Cluster &c = clusters[l];
+
+ // Don't consider merging if the edge is unlikely.
+ if (c.bestPred.from == -1 || c.bestPred.weight * 10 <= c.initialWeight)
+ continue;
+
+ int predL = getLeader(leaders, c.bestPred.from);
+ // Already in the same cluster.
+ if (l == predL)
+ continue;
+
+ Cluster *predC = &clusters[predL];
+ if (c.size + predC->size > maxClusterSize)
+ continue;
+
+ if (isNewDensityBad(*predC, c))
+ continue;
+
+ leaders[l] = predL;
+ mergeClusters(clusters, *predC, predL, c, l);
+ }
+ // Sort remaining non-empty clusters by density.
+ sorted.clear();
+ for (int i = 0, e = (int)clusters.size(); i != e; ++i)
+ if (clusters[i].size > 0)
+ sorted.push_back(i);
+ llvm::stable_sort(sorted, [&](int a, int b) {
+ return clusters[a].getDensity() > clusters[b].getDensity();
+ });
+
+ DenseMap<const InputSection *, size_t> orderMap;
+
+ // Sections will be sorted by decreasing order. Absent sections will have
+ // priority 0 and be placed at the end of sections.
+ // NB: This is opposite from COFF/ELF to be compatible with the existing
+ // order-file code.
+ int curOrder = highestAvailablePriority;
+ for (int leader : sorted) {
+ for (int i = leader;;) {
+ orderMap[sections[i]] = curOrder--;
+ i = clusters[i].next;
+ if (i == leader)
+ break;
+ }
+ }
+ if (!config->printSymbolOrder.empty()) {
+ std::error_code ec;
+ raw_fd_ostream os(config->printSymbolOrder, ec, sys::fs::OF_None);
+ if (ec) {
+ error("cannot open " + config->printSymbolOrder + ": " + ec.message());
+ return orderMap;
+ }
+ // Print the symbols ordered by C3, in the order of decreasing curOrder
+ // Instead of sorting all the orderMap, just repeat the loops above.
+ for (int leader : sorted)
+ for (int i = leader;;) {
+ const InputSection *isec = sections[i];
+ // Search all the symbols in the file of the section
+ // and find out a Defined symbol with name that is within the
+ // section.
+ for (Symbol *sym : isec->getFile()->symbols) {
+ if (auto *d = dyn_cast_or_null<Defined>(sym)) {
+ if (d->isec == isec)
+ os << sym->getName() << "\n";
+ }
+ }
+ i = clusters[i].next;
+ if (i == leader)
+ break;
+ }
+ }
+
+ return orderMap;
+}
+
+std::optional<size_t>
+macho::PriorityBuilder::getSymbolPriority(const Defined *sym) {
+ if (sym->isAbsolute())
+ return std::nullopt;
+
+ auto it = priorities.find(sym->getName());
+ if (it == priorities.end())
+ return std::nullopt;
+ const SymbolPriorityEntry &entry = it->second;
+ const InputFile *f = sym->isec->getFile();
+ if (!f)
+ return entry.anyObjectFile;
+ // We don't use toString(InputFile *) here because it returns the full path
+ // for object files, and we only want the basename.
+ StringRef filename;
+ if (f->archiveName.empty())
+ filename = path::filename(f->getName());
+ else
+ filename = saver().save(path::filename(f->archiveName) + "(" +
+ path::filename(f->getName()) + ")");
+ return std::max(entry.objectFiles.lookup(filename), entry.anyObjectFile);
+}
+
+void macho::PriorityBuilder::extractCallGraphProfile() {
+ TimeTraceScope timeScope("Extract call graph profile");
+ bool hasOrderFile = !priorities.empty();
+ for (const InputFile *file : inputFiles) {
+ auto *obj = dyn_cast_or_null<ObjFile>(file);
+ if (!obj)
+ continue;
+ for (const CallGraphEntry &entry : obj->callGraph) {
+ assert(entry.fromIndex < obj->symbols.size() &&
+ entry.toIndex < obj->symbols.size());
+ auto *fromSym = dyn_cast_or_null<Defined>(obj->symbols[entry.fromIndex]);
+ auto *toSym = dyn_cast_or_null<Defined>(obj->symbols[entry.toIndex]);
+ if (fromSym && toSym &&
+ (!hasOrderFile ||
+ (!getSymbolPriority(fromSym) && !getSymbolPriority(toSym))))
+ callGraphProfile[{fromSym->isec, toSym->isec}] += entry.count;
+ }
+ }
+}
+
+void macho::PriorityBuilder::parseOrderFile(StringRef path) {
+ assert(callGraphProfile.empty() &&
+ "Order file must be parsed before call graph profile is processed");
+ std::optional<MemoryBufferRef> buffer = readFile(path);
+ if (!buffer) {
+ error("Could not read order file at " + path);
+ return;
+ }
+
+ MemoryBufferRef mbref = *buffer;
+ for (StringRef line : args::getLines(mbref)) {
+ StringRef objectFile, symbol;
+ line = line.take_until([](char c) { return c == '#'; }); // ignore comments
+ line = line.ltrim();
+
+ CPUType cpuType = StringSwitch<CPUType>(line)
+ .StartsWith("i386:", CPU_TYPE_I386)
+ .StartsWith("x86_64:", CPU_TYPE_X86_64)
+ .StartsWith("arm:", CPU_TYPE_ARM)
+ .StartsWith("arm64:", CPU_TYPE_ARM64)
+ .StartsWith("ppc:", CPU_TYPE_POWERPC)
+ .StartsWith("ppc64:", CPU_TYPE_POWERPC64)
+ .Default(CPU_TYPE_ANY);
+
+ if (cpuType != CPU_TYPE_ANY && cpuType != target->cpuType)
+ continue;
+
+ // Drop the CPU type as well as the colon
+ if (cpuType != CPU_TYPE_ANY)
+ line = line.drop_until([](char c) { return c == ':'; }).drop_front();
+
+ constexpr std::array<StringRef, 2> fileEnds = {".o:", ".o):"};
+ for (StringRef fileEnd : fileEnds) {
+ size_t pos = line.find(fileEnd);
+ if (pos != StringRef::npos) {
+ // Split the string around the colon
+ objectFile = line.take_front(pos + fileEnd.size() - 1);
+ line = line.drop_front(pos + fileEnd.size());
+ break;
+ }
+ }
+ symbol = line.trim();
+
+ if (!symbol.empty()) {
+ SymbolPriorityEntry &entry = priorities[symbol];
+ if (!objectFile.empty())
+ entry.objectFiles.insert(
+ std::make_pair(objectFile, highestAvailablePriority));
+ else
+ entry.anyObjectFile =
+ std::max(entry.anyObjectFile, highestAvailablePriority);
+ }
+
+ --highestAvailablePriority;
+ }
+}
+
+DenseMap<const InputSection *, size_t>
+macho::PriorityBuilder::buildInputSectionPriorities() {
+ DenseMap<const InputSection *, size_t> sectionPriorities;
+ if (config->callGraphProfileSort) {
+ // Sort sections by the profile data provided by __LLVM,__cg_profile
+ // sections.
+ //
+ // This first builds a call graph based on the profile data then merges
+ // sections according to the C³ heuristic. All clusters are then sorted by a
+ // density metric to further improve locality.
+ TimeTraceScope timeScope("Call graph profile sort");
+ sectionPriorities = CallGraphSort(callGraphProfile).run();
+ }
+
+ if (priorities.empty())
+ return sectionPriorities;
+
+ auto addSym = [&](const Defined *sym) {
+ std::optional<size_t> symbolPriority = getSymbolPriority(sym);
+ if (!symbolPriority)
+ return;
+ size_t &priority = sectionPriorities[sym->isec];
+ priority = std::max(priority, *symbolPriority);
+ };
+
+ // TODO: Make sure this handles weak symbols correctly.
+ for (const InputFile *file : inputFiles) {
+ if (isa<ObjFile>(file))
+ for (Symbol *sym : file->symbols)
+ if (auto *d = dyn_cast_or_null<Defined>(sym))
+ addSym(d);
+ }
+
+ return sectionPriorities;
+}
--- /dev/null
+//===- SectionPriorities.h --------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLD_MACHO_SECTION_PRIORITIES_H
+#define LLD_MACHO_SECTION_PRIORITIES_H
+
+#include "InputSection.h"
+#include "llvm/ADT/DenseMap.h"
+
+namespace lld::macho {
+
+using SectionPair = std::pair<const InputSection *, const InputSection *>;
+
+class PriorityBuilder {
+public:
+ // Reads every input section's call graph profile, and combines them into
+ // callGraphProfile. If an order file is present, any edges where one or both
+ // of the vertices are specified in the order file are discarded.
+ void extractCallGraphProfile();
+
+ // Reads the order file at `path` into config->priorities.
+ //
+ // An order file has one entry per line, in the following format:
+ //
+ // <cpu>:<object file>:<symbol name>
+ //
+ // <cpu> and <object file> are optional. If not specified, then that entry
+ // matches any symbol of that name. Parsing this format is not quite
+ // straightforward because the symbol name itself can contain colons, so when
+ // encountering a colon, we consider the preceding characters to decide if it
+ // can be a valid CPU type or file path.
+ //
+ // If a symbol is matched by multiple entries, then it takes the
+ // lowest-ordered entry (the one nearest to the front of the list.)
+ //
+ // The file can also have line comments that start with '#'.
+ void parseOrderFile(StringRef path);
+
+ // Returns layout priorities for some or all input sections. Sections are laid
+ // out in decreasing order; that is, a higher priority section will be closer
+ // to the beginning of its output section.
+ //
+ // If either an order file or a call graph profile are present, this is used
+ // as the source of priorities. If both are present, the order file takes
+ // precedence, but the call graph profile is still used for symbols that don't
+ // appear in the order file. If neither is present, an empty map is returned.
+ //
+ // Each section gets assigned the priority of the highest-priority symbol it
+ // contains.
+ llvm::DenseMap<const InputSection *, size_t> buildInputSectionPriorities();
+
+private:
+ // The symbol with the highest priority should be ordered first in the output
+ // section (modulo input section contiguity constraints). Using priority
+ // (highest first) instead of order (lowest first) has the convenient property
+ // that the default-constructed zero priority -- for symbols/sections without
+ // a user-defined order -- naturally ends up putting them at the end of the
+ // output.
+ struct SymbolPriorityEntry {
+ // The priority given to a matching symbol, regardless of which object file
+ // it originated from.
+ size_t anyObjectFile = 0;
+ // The priority given to a matching symbol from a particular object file.
+ llvm::DenseMap<llvm::StringRef, size_t> objectFiles;
+ };
+
+ std::optional<size_t> getSymbolPriority(const Defined *sym);
+ llvm::DenseMap<llvm::StringRef, SymbolPriorityEntry> priorities;
+ llvm::MapVector<SectionPair, uint64_t> callGraphProfile;
+};
+
+extern PriorityBuilder priorityBuilder;
+} // namespace lld::macho
+
+#endif
#include "ConcatOutputSection.h"
#include "Config.h"
#include "InputFiles.h"
+#include "InputSection.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
+#include "llvm/Demangle/Demangle.h"
using namespace llvm;
using namespace lld;
return {sym, p.second};
}
+namespace {
+struct DuplicateSymbolDiag {
+ // Pair containing source location and source file
+ const std::pair<std::string, std::string> src1;
+ const std::pair<std::string, std::string> src2;
+ const Symbol *sym;
+
+ DuplicateSymbolDiag(const std::pair<std::string, std::string> src1,
+ const std::pair<std::string, std::string> src2,
+ const Symbol *sym)
+ : src1(src1), src2(src2), sym(sym) {}
+};
+SmallVector<DuplicateSymbolDiag> dupSymDiags;
+} // namespace
+
Defined *SymbolTable::addDefined(StringRef name, InputFile *file,
InputSection *isec, uint64_t value,
uint64_t size, bool isWeakDef,
bool isPrivateExtern, bool isThumb,
- bool isReferencedDynamically,
- bool noDeadStrip) {
- Symbol *s;
- bool wasInserted;
+ bool isReferencedDynamically, bool noDeadStrip,
+ bool isWeakDefCanBeHidden) {
bool overridesWeakDef = false;
- std::tie(s, wasInserted) = insert(name, file);
+ auto [s, wasInserted] = insert(name, file);
- assert(!isWeakDef || (isa<BitcodeFile>(file) && !isec) ||
- (isa<ObjFile>(file) && file == isec->getFile()));
+ assert(!file || !isa<BitcodeFile>(file) || !isec);
if (!wasInserted) {
if (auto *defined = dyn_cast<Defined>(s)) {
if (isWeakDef) {
+ // See further comment in createDefined() in InputFiles.cpp
if (defined->isWeakDef()) {
- // Both old and new symbol weak (e.g. inline function in two TUs):
- // If one of them isn't private extern, the merged symbol isn't.
defined->privateExtern &= isPrivateExtern;
+ defined->weakDefCanBeHidden &= isWeakDefCanBeHidden;
defined->referencedDynamically |= isReferencedDynamically;
defined->noDeadStrip |= noDeadStrip;
-
- // FIXME: Handle this for bitcode files.
- // FIXME: We currently only do this if both symbols are weak.
- // We could do this if either is weak (but getting the
- // case where !isWeakDef && defined->isWeakDef() right
- // requires some care and testing).
- if (auto concatIsec = dyn_cast_or_null<ConcatInputSection>(isec))
- concatIsec->wasCoalesced = true;
}
-
+ // FIXME: Handle this for bitcode files.
+ if (auto concatIsec = dyn_cast_or_null<ConcatInputSection>(isec))
+ concatIsec->wasCoalesced = true;
return defined;
}
- if (!defined->isWeakDef())
- error("duplicate symbol: " + name + "\n>>> defined in " +
- toString(defined->getFile()) + "\n>>> defined in " +
- toString(file));
+
+ if (defined->isWeakDef()) {
+ // FIXME: Handle this for bitcode files.
+ if (auto concatIsec =
+ dyn_cast_or_null<ConcatInputSection>(defined->isec)) {
+ concatIsec->wasCoalesced = true;
+ concatIsec->symbols.erase(llvm::find(concatIsec->symbols, defined));
+ }
+ } else {
+ std::string srcLoc1 = defined->getSourceLocation();
+ std::string srcLoc2 = isec ? isec->getSourceLocation(value) : "";
+ std::string srcFile1 = toString(defined->getFile());
+ std::string srcFile2 = toString(file);
+
+ dupSymDiags.push_back({make_pair(srcLoc1, srcFile1),
+ make_pair(srcLoc2, srcFile2), defined});
+ }
+
} else if (auto *dysym = dyn_cast<DylibSymbol>(s)) {
overridesWeakDef = !isWeakDef && dysym->isWeakDef();
dysym->unreference();
+ } else if (auto *undef = dyn_cast<Undefined>(s)) {
+ // Preserve the original bitcode file name (instead of using the object
+ // file name).
+ if (undef->wasBitcodeSymbol)
+ file = undef->getFile();
}
// Defined symbols take priority over other types of symbols, so in case
// of a name conflict, we fall through to the replaceSymbol() call below.
}
+ // With -flat_namespace, all extern symbols in dylibs are interposable.
+ // FIXME: Add support for `-interposable` (PR53680).
+ bool interposable = config->namespaceKind == NamespaceKind::flat &&
+ config->outputType != MachO::MH_EXECUTE &&
+ !isPrivateExtern;
Defined *defined = replaceSymbol<Defined>(
s, name, file, isec, value, size, isWeakDef, /*isExternal=*/true,
- isPrivateExtern, isThumb, isReferencedDynamically, noDeadStrip);
- defined->overridesWeakDef = overridesWeakDef;
+ isPrivateExtern, /*includeInSymtab=*/true, isThumb,
+ isReferencedDynamically, noDeadStrip, overridesWeakDef,
+ isWeakDefCanBeHidden, interposable);
return defined;
}
+Defined *SymbolTable::aliasDefined(Defined *src, StringRef target,
+ InputFile *newFile, bool makePrivateExtern) {
+ bool isPrivateExtern = makePrivateExtern || src->privateExtern;
+ return addDefined(target, newFile, src->isec, src->value, src->size,
+ src->isWeakDef(), isPrivateExtern, src->thumb,
+ src->referencedDynamically, src->noDeadStrip,
+ src->weakDefCanBeHidden);
+}
+
Symbol *SymbolTable::addUndefined(StringRef name, InputFile *file,
bool isWeakRef) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(name, file);
+ auto [s, wasInserted] = insert(name, file);
RefState refState = isWeakRef ? RefState::Weak : RefState::Strong;
if (wasInserted)
- replaceSymbol<Undefined>(s, name, file, refState);
- else if (auto *lazy = dyn_cast<LazySymbol>(s))
+ replaceSymbol<Undefined>(s, name, file, refState,
+ /*wasBitcodeSymbol=*/false);
+ else if (auto *lazy = dyn_cast<LazyArchive>(s))
lazy->fetchArchiveMember();
+ else if (isa<LazyObject>(s))
+ extract(*s->getFile(), s->getName());
else if (auto *dynsym = dyn_cast<DylibSymbol>(s))
dynsym->reference(refState);
else if (auto *undefined = dyn_cast<Undefined>(s))
Symbol *SymbolTable::addCommon(StringRef name, InputFile *file, uint64_t size,
uint32_t align, bool isPrivateExtern) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(name, file);
+ auto [s, wasInserted] = insert(name, file);
if (!wasInserted) {
if (auto *common = dyn_cast<CommonSymbol>(s)) {
Symbol *SymbolTable::addDylib(StringRef name, DylibFile *file, bool isWeakDef,
bool isTlv) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(name, file);
+ auto [s, wasInserted] = insert(name, file);
RefState refState = RefState::Unreferenced;
if (!wasInserted) {
return addDylib(name, /*file=*/nullptr, /*isWeakDef=*/false, /*isTlv=*/false);
}
-Symbol *SymbolTable::addLazy(StringRef name, ArchiveFile *file,
- const object::Archive::Symbol &sym) {
- Symbol *s;
- bool wasInserted;
- std::tie(s, wasInserted) = insert(name, file);
+Symbol *SymbolTable::addLazyArchive(StringRef name, ArchiveFile *file,
+ const object::Archive::Symbol &sym) {
+ auto [s, wasInserted] = insert(name, file);
- if (wasInserted)
- replaceSymbol<LazySymbol>(s, file, sym);
- else if (isa<Undefined>(s) || (isa<DylibSymbol>(s) && s->isWeakDef()))
+ if (wasInserted) {
+ replaceSymbol<LazyArchive>(s, file, sym);
+ } else if (isa<Undefined>(s)) {
file->fetch(sym);
+ } else if (auto *dysym = dyn_cast<DylibSymbol>(s)) {
+ if (dysym->isWeakDef()) {
+ if (dysym->getRefState() != RefState::Unreferenced)
+ file->fetch(sym);
+ else
+ replaceSymbol<LazyArchive>(s, file, sym);
+ }
+ }
+ return s;
+}
+
+Symbol *SymbolTable::addLazyObject(StringRef name, InputFile &file) {
+ auto [s, wasInserted] = insert(name, &file);
+
+ if (wasInserted) {
+ replaceSymbol<LazyObject>(s, file, name);
+ } else if (isa<Undefined>(s)) {
+ extract(file, name);
+ } else if (auto *dysym = dyn_cast<DylibSymbol>(s)) {
+ if (dysym->isWeakDef()) {
+ if (dysym->getRefState() != RefState::Unreferenced)
+ extract(file, name);
+ else
+ replaceSymbol<LazyObject>(s, file, name);
+ }
+ }
return s;
}
uint64_t value, bool isPrivateExtern,
bool includeInSymtab,
bool referencedDynamically) {
- Defined *s = addDefined(name, nullptr, isec, value, /*size=*/0,
- /*isWeakDef=*/false, isPrivateExtern,
- /*isThumb=*/false, referencedDynamically,
- /*noDeadStrip=*/false);
+ assert(!isec || !isec->getFile()); // See makeSyntheticInputSection().
+ Defined *s =
+ addDefined(name, /*file=*/nullptr, isec, value, /*size=*/0,
+ /*isWeakDef=*/false, isPrivateExtern, /*isThumb=*/false,
+ referencedDynamically, /*noDeadStrip=*/false,
+ /*isWeakDefCanBeHidden=*/false);
s->includeInSymtab = includeInSymtab;
return s;
}
static void handleSectionBoundarySymbol(const Undefined &sym, StringRef segSect,
Boundary which) {
- StringRef segName, sectName;
- std::tie(segName, sectName) = segSect.split('$');
+ auto [segName, sectName] = segSect.split('$');
// Attach the symbol to any InputSection that will end up in the right
// OutputSection -- it doesn't matter which one we pick.
}
if (!osec) {
- ConcatInputSection *isec = make<ConcatInputSection>(segName, sectName);
+ ConcatInputSection *isec = makeSyntheticInputSection(segName, sectName);
// This runs after markLive() and is only called for Undefineds that are
// live. Marking the isec live ensures an OutputSection is created that the
seg->segmentEndSymbols.push_back(createBoundarySymbol(sym));
}
-void lld::macho::treatUndefinedSymbol(const Undefined &sym, StringRef source) {
+// Try to find a definition for an undefined symbol.
+// Returns true if a definition was found and no diagnostics are needed.
+static bool recoverFromUndefinedSymbol(const Undefined &sym) {
// Handle start/end symbols.
StringRef name = sym.getName();
- if (name.consume_front("section$start$"))
- return handleSectionBoundarySymbol(sym, name, Boundary::Start);
- if (name.consume_front("section$end$"))
- return handleSectionBoundarySymbol(sym, name, Boundary::End);
- if (name.consume_front("segment$start$"))
- return handleSegmentBoundarySymbol(sym, name, Boundary::Start);
- if (name.consume_front("segment$end$"))
- return handleSegmentBoundarySymbol(sym, name, Boundary::End);
+ if (name.consume_front("section$start$")) {
+ handleSectionBoundarySymbol(sym, name, Boundary::Start);
+ return true;
+ }
+ if (name.consume_front("section$end$")) {
+ handleSectionBoundarySymbol(sym, name, Boundary::End);
+ return true;
+ }
+ if (name.consume_front("segment$start$")) {
+ handleSegmentBoundarySymbol(sym, name, Boundary::Start);
+ return true;
+ }
+ if (name.consume_front("segment$end$")) {
+ handleSegmentBoundarySymbol(sym, name, Boundary::End);
+ return true;
+ }
+
+ // Leave dtrace symbols, since we will handle them when we do the relocation
+ if (name.startswith("___dtrace_"))
+ return true;
// Handle -U.
if (config->explicitDynamicLookups.count(sym.getName())) {
symtab->addDynamicLookup(sym.getName());
- return;
+ return true;
}
// Handle -undefined.
- auto message = [source, &sym]() {
- std::string message = "undefined symbol";
- if (config->archMultiple)
- message += (" for arch " + getArchitectureName(config->arch())).str();
- message += ": " + toString(sym);
- if (!source.empty())
- message += "\n>>> referenced by " + source.str();
- else
- message += "\n>>> referenced by " + toString(sym.getFile());
- return message;
- };
- switch (config->undefinedSymbolTreatment) {
- case UndefinedSymbolTreatment::error:
- error(message());
- break;
- case UndefinedSymbolTreatment::warning:
- warn(message());
- LLVM_FALLTHROUGH;
- case UndefinedSymbolTreatment::dynamic_lookup:
- case UndefinedSymbolTreatment::suppress:
+ if (config->undefinedSymbolTreatment ==
+ UndefinedSymbolTreatment::dynamic_lookup ||
+ config->undefinedSymbolTreatment == UndefinedSymbolTreatment::suppress) {
symtab->addDynamicLookup(sym.getName());
- break;
- case UndefinedSymbolTreatment::unknown:
- llvm_unreachable("unknown -undefined TREATMENT");
+ return true;
}
+
+ // We do not return true here, as we still need to print diagnostics.
+ if (config->undefinedSymbolTreatment == UndefinedSymbolTreatment::warning)
+ symtab->addDynamicLookup(sym.getName());
+
+ return false;
+}
+
+namespace {
+struct UndefinedDiag {
+ struct SectionAndOffset {
+ const InputSection *isec;
+ uint64_t offset;
+ };
+
+ std::vector<SectionAndOffset> codeReferences;
+ std::vector<std::string> otherReferences;
+};
+
+MapVector<const Undefined *, UndefinedDiag> undefs;
+}
+
+void macho::reportPendingDuplicateSymbols() {
+ for (const auto &duplicate : dupSymDiags) {
+ if (!config->deadStripDuplicates || duplicate.sym->isLive()) {
+ std::string message =
+ "duplicate symbol: " + toString(*duplicate.sym) + "\n>>> defined in ";
+ if (!duplicate.src1.first.empty())
+ message += duplicate.src1.first + "\n>>> ";
+ message += duplicate.src1.second + "\n>>> defined in ";
+ if (!duplicate.src2.first.empty())
+ message += duplicate.src2.first + "\n>>> ";
+ error(message + duplicate.src2.second);
+ }
+ }
+}
+
+// Check whether the definition name def is a mangled function name that matches
+// the reference name ref.
+static bool canSuggestExternCForCXX(StringRef ref, StringRef def) {
+ llvm::ItaniumPartialDemangler d;
+ std::string name = def.str();
+ if (d.partialDemangle(name.c_str()))
+ return false;
+ char *buf = d.getFunctionName(nullptr, nullptr);
+ if (!buf)
+ return false;
+ bool ret = ref == buf;
+ free(buf);
+ return ret;
+}
+
+// Suggest an alternative spelling of an "undefined symbol" diagnostic. Returns
+// the suggested symbol, which is either in the symbol table, or in the same
+// file of sym.
+static const Symbol *getAlternativeSpelling(const Undefined &sym,
+ std::string &pre_hint,
+ std::string &post_hint) {
+ DenseMap<StringRef, const Symbol *> map;
+ if (sym.getFile() && sym.getFile()->kind() == InputFile::ObjKind) {
+ // Build a map of local defined symbols.
+ for (const Symbol *s : sym.getFile()->symbols)
+ if (auto *defined = dyn_cast_or_null<Defined>(s))
+ if (!defined->isExternal())
+ map.try_emplace(s->getName(), s);
+ }
+
+ auto suggest = [&](StringRef newName) -> const Symbol * {
+ // If defined locally.
+ if (const Symbol *s = map.lookup(newName))
+ return s;
+
+ // If in the symbol table and not undefined.
+ if (const Symbol *s = symtab->find(newName))
+ if (dyn_cast<Undefined>(s) == nullptr)
+ return s;
+
+ return nullptr;
+ };
+
+ // This loop enumerates all strings of Levenshtein distance 1 as typo
+ // correction candidates and suggests the one that exists as a non-undefined
+ // symbol.
+ StringRef name = sym.getName();
+ for (size_t i = 0, e = name.size(); i != e + 1; ++i) {
+ // Insert a character before name[i].
+ std::string newName = (name.substr(0, i) + "0" + name.substr(i)).str();
+ for (char c = '0'; c <= 'z'; ++c) {
+ newName[i] = c;
+ if (const Symbol *s = suggest(newName))
+ return s;
+ }
+ if (i == e)
+ break;
+
+ // Substitute name[i].
+ newName = std::string(name);
+ for (char c = '0'; c <= 'z'; ++c) {
+ newName[i] = c;
+ if (const Symbol *s = suggest(newName))
+ return s;
+ }
+
+ // Transpose name[i] and name[i+1]. This is of edit distance 2 but it is
+ // common.
+ if (i + 1 < e) {
+ newName[i] = name[i + 1];
+ newName[i + 1] = name[i];
+ if (const Symbol *s = suggest(newName))
+ return s;
+ }
+
+ // Delete name[i].
+ newName = (name.substr(0, i) + name.substr(i + 1)).str();
+ if (const Symbol *s = suggest(newName))
+ return s;
+ }
+
+ // Case mismatch, e.g. Foo vs FOO.
+ for (auto &it : map)
+ if (name.equals_insensitive(it.first))
+ return it.second;
+ for (Symbol *sym : symtab->getSymbols())
+ if (dyn_cast<Undefined>(sym) == nullptr &&
+ name.equals_insensitive(sym->getName()))
+ return sym;
+
+ // The reference may be a mangled name while the definition is not. Suggest a
+ // missing extern "C".
+ if (name.startswith("__Z")) {
+ std::string buf = name.str();
+ llvm::ItaniumPartialDemangler d;
+ if (!d.partialDemangle(buf.c_str()))
+ if (char *buf = d.getFunctionName(nullptr, nullptr)) {
+ const Symbol *s = suggest((Twine("_") + buf).str());
+ free(buf);
+ if (s) {
+ pre_hint = ": extern \"C\" ";
+ return s;
+ }
+ }
+ } else {
+ StringRef name_without_underscore = name;
+ name_without_underscore.consume_front("_");
+ const Symbol *s = nullptr;
+ for (auto &it : map)
+ if (canSuggestExternCForCXX(name_without_underscore, it.first)) {
+ s = it.second;
+ break;
+ }
+ if (!s)
+ for (Symbol *sym : symtab->getSymbols())
+ if (canSuggestExternCForCXX(name_without_underscore, sym->getName())) {
+ s = sym;
+ break;
+ }
+ if (s) {
+ pre_hint = " to declare ";
+ post_hint = " as extern \"C\"?";
+ return s;
+ }
+ }
+
+ return nullptr;
+}
+
+static void reportUndefinedSymbol(const Undefined &sym,
+ const UndefinedDiag &locations,
+ bool correctSpelling) {
+ std::string message = "undefined symbol";
+ if (config->archMultiple)
+ message += (" for arch " + getArchitectureName(config->arch())).str();
+ message += ": " + toString(sym);
+
+ const size_t maxUndefinedReferences = 3;
+ size_t i = 0;
+ for (const std::string &loc : locations.otherReferences) {
+ if (i >= maxUndefinedReferences)
+ break;
+ message += "\n>>> referenced by " + loc;
+ ++i;
+ }
+
+ for (const UndefinedDiag::SectionAndOffset &loc : locations.codeReferences) {
+ if (i >= maxUndefinedReferences)
+ break;
+ message += "\n>>> referenced by ";
+ std::string src = loc.isec->getSourceLocation(loc.offset);
+ if (!src.empty())
+ message += src + "\n>>> ";
+ message += loc.isec->getLocation(loc.offset);
+ ++i;
+ }
+
+ size_t totalReferences =
+ locations.otherReferences.size() + locations.codeReferences.size();
+ if (totalReferences > i)
+ message +=
+ ("\n>>> referenced " + Twine(totalReferences - i) + " more times")
+ .str();
+
+ if (correctSpelling) {
+ std::string pre_hint = ": ", post_hint;
+ if (const Symbol *corrected =
+ getAlternativeSpelling(sym, pre_hint, post_hint)) {
+ message +=
+ "\n>>> did you mean" + pre_hint + toString(*corrected) + post_hint;
+ if (corrected->getFile())
+ message += "\n>>> defined in: " + toString(corrected->getFile());
+ }
+ }
+
+ if (config->undefinedSymbolTreatment == UndefinedSymbolTreatment::error)
+ error(message);
+ else if (config->undefinedSymbolTreatment ==
+ UndefinedSymbolTreatment::warning)
+ warn(message);
+ else
+ assert(false && "diagnostics make sense for -undefined error|warning only");
+}
+
+void macho::reportPendingUndefinedSymbols() {
+ // Enable spell corrector for the first 2 diagnostics.
+ for (const auto &[i, undef] : llvm::enumerate(undefs))
+ reportUndefinedSymbol(*undef.first, undef.second, i < 2);
+
+ // This function is called multiple times during execution. Clear the printed
+ // diagnostics to avoid printing the same things again the next time.
+ undefs.clear();
+}
+
+void macho::treatUndefinedSymbol(const Undefined &sym, StringRef source) {
+ if (recoverFromUndefinedSymbol(sym))
+ return;
+
+ undefs[&sym].otherReferences.push_back(source.str());
+}
+
+void macho::treatUndefinedSymbol(const Undefined &sym, const InputSection *isec,
+ uint64_t offset) {
+ if (recoverFromUndefinedSymbol(sym))
+ return;
+
+ undefs[&sym].codeReferences.push_back({isec, offset});
}
-SymbolTable *macho::symtab;
+std::unique_ptr<SymbolTable> macho::symtab;
#include "llvm/ADT/DenseMap.h"
#include "llvm/Object/Archive.h"
-namespace lld {
-namespace macho {
+namespace lld::macho {
class ArchiveFile;
class DylibFile;
Defined *addDefined(StringRef name, InputFile *, InputSection *,
uint64_t value, uint64_t size, bool isWeakDef,
bool isPrivateExtern, bool isThumb,
- bool isReferencedDynamically, bool noDeadStrip);
+ bool isReferencedDynamically, bool noDeadStrip,
+ bool isWeakDefCanBeHidden);
+
+ Defined *aliasDefined(Defined *src, StringRef target, InputFile *newFile,
+ bool makePrivateExtern = false);
Symbol *addUndefined(StringRef name, InputFile *, bool isWeakRef);
Symbol *addDylib(StringRef name, DylibFile *file, bool isWeakDef, bool isTlv);
Symbol *addDynamicLookup(StringRef name);
- Symbol *addLazy(StringRef name, ArchiveFile *file,
- const llvm::object::Archive::Symbol &sym);
+ Symbol *addLazyArchive(StringRef name, ArchiveFile *file,
+ const llvm::object::Archive::Symbol &sym);
+ Symbol *addLazyObject(StringRef name, InputFile &file);
Defined *addSynthetic(StringRef name, InputSection *, uint64_t value,
bool isPrivateExtern, bool includeInSymtab,
std::vector<Symbol *> symVector;
};
-void treatUndefinedSymbol(const Undefined &, StringRef source = "");
+void reportPendingUndefinedSymbols();
+void reportPendingDuplicateSymbols();
+
+// Call reportPendingUndefinedSymbols() to emit diagnostics.
+void treatUndefinedSymbol(const Undefined &, StringRef source);
+void treatUndefinedSymbol(const Undefined &, const InputSection *,
+ uint64_t offset);
-extern SymbolTable *symtab;
+extern std::unique_ptr<SymbolTable> symtab;
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "Symbols.h"
#include "InputFiles.h"
#include "SyntheticSections.h"
+#include "llvm/Demangle/Demangle.h"
using namespace llvm;
using namespace lld;
using namespace lld::macho;
-// Returns a symbol for an error message.
-static std::string demangle(StringRef symName) {
- if (config->demangle)
- return demangleItanium(symName);
- return std::string(symName);
+static_assert(sizeof(void *) != 8 || sizeof(Symbol) == 56,
+ "Try to minimize Symbol's size; we create many instances");
+
+// The Microsoft ABI doesn't support using parent class tail padding for child
+// members, hence the _MSC_VER check.
+#if !defined(_MSC_VER)
+static_assert(sizeof(void *) != 8 || sizeof(Defined) == 88,
+ "Try to minimize Defined's size; we create many instances");
+#endif
+
+static_assert(sizeof(SymbolUnion) == sizeof(Defined),
+ "Defined should be the largest Symbol kind");
+
+// Returns a symbol name for an error message.
+static std::string maybeDemangleSymbol(StringRef symName) {
+ if (config->demangle) {
+ symName.consume_front("_");
+ return demangle(symName.str());
+ }
+ return symName.str();
}
-std::string lld::toString(const Symbol &sym) { return demangle(sym.getName()); }
+std::string lld::toString(const Symbol &sym) {
+ return maybeDemangleSymbol(sym.getName());
+}
std::string lld::toMachOString(const object::Archive::Symbol &b) {
- return demangle(b.getName());
+ return maybeDemangleSymbol(b.getName());
}
uint64_t Symbol::getStubVA() const { return in.stubs->getVA(stubsIndex); }
+uint64_t Symbol::getLazyPtrVA() const {
+ return in.lazyPointers->getVA(stubsIndex);
+}
uint64_t Symbol::getGotVA() const { return in.got->getVA(gotIndex); }
uint64_t Symbol::getTlvVA() const { return in.tlvPointers->getVA(gotIndex); }
-bool Symbol::isLive() const {
- if (isa<DylibSymbol>(this) || isa<Undefined>(this))
- return used;
-
- if (auto *d = dyn_cast<Defined>(this)) {
- // Non-absolute symbols might be alive because their section is
- // no_dead_strip or live_support. In that case, the section will know
- // that it's live but `used` might be false. Non-absolute symbols always
- // have to use the section's `live` bit as source of truth.
- if (d->isAbsolute())
- return used;
- return d->isec->canonical()->isLive(d->value);
+Defined::Defined(StringRefZ name, InputFile *file, InputSection *isec,
+ uint64_t value, uint64_t size, bool isWeakDef, bool isExternal,
+ bool isPrivateExtern, bool includeInSymtab, bool isThumb,
+ bool isReferencedDynamically, bool noDeadStrip,
+ bool canOverrideWeakDef, bool isWeakDefCanBeHidden,
+ bool interposable)
+ : Symbol(DefinedKind, name, file), overridesWeakDef(canOverrideWeakDef),
+ privateExtern(isPrivateExtern), includeInSymtab(includeInSymtab),
+ wasIdenticalCodeFolded(false), thumb(isThumb),
+ referencedDynamically(isReferencedDynamically), noDeadStrip(noDeadStrip),
+ interposable(interposable), weakDefCanBeHidden(isWeakDefCanBeHidden),
+ weakDef(isWeakDef), external(isExternal), isec(isec), value(value),
+ size(size) {
+ if (isec) {
+ isec->symbols.push_back(this);
+ // Maintain sorted order.
+ for (auto it = isec->symbols.rbegin(), rend = isec->symbols.rend();
+ it != rend; ++it) {
+ auto next = std::next(it);
+ if (next == rend)
+ break;
+ if ((*it)->value < (*next)->value)
+ std::swap(*next, *it);
+ else
+ break;
+ }
}
+}
- assert(!isa<CommonSymbol>(this) &&
- "replaceCommonSymbols() runs before dead code stripping, and isLive() "
- "should only be called after dead code stripping");
-
- // Assume any other kind of symbol is live.
- return true;
+bool Defined::isTlv() const {
+ return !isAbsolute() && isThreadLocalVariables(isec->getFlags());
}
uint64_t Defined::getVA() const {
if (isAbsolute())
return value;
- if (!isec->canonical()->isFinal) {
+ if (!isec->isFinal) {
// A target arch that does not use thunks ought never ask for
// the address of a function that has not yet been finalized.
assert(target->usesThunks());
// expedient to return a contrived out-of-range address.
return TargetInfo::outOfRangeVA;
}
- return isec->canonical()->getVA(value);
+ return isec->getVA(value);
+}
+
+ObjFile *Defined::getObjectFile() const {
+ return isec ? dyn_cast_or_null<ObjFile>(isec->getFile()) : nullptr;
+}
+
+void Defined::canonicalize() {
+ if (unwindEntry)
+ unwindEntry = unwindEntry->canonical();
+ if (isec)
+ isec = isec->canonical();
+}
+
+std::string Defined::getSourceLocation() {
+ if (!isec)
+ return {};
+ return isec->getSourceLocation(value);
}
uint64_t DylibSymbol::getVA() const {
return isInStubs() ? getStubVA() : Symbol::getVA();
}
-void LazySymbol::fetchArchiveMember() { getFile()->fetch(sym); }
+void LazyArchive::fetchArchiveMember() { getFile()->fetch(sym); }
#ifndef LLD_MACHO_SYMBOLS_H
#define LLD_MACHO_SYMBOLS_H
+#include "Config.h"
#include "InputFiles.h"
-#include "InputSection.h"
#include "Target.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Strings.h"
+
#include "llvm/Object/Archive.h"
#include "llvm/Support/MathExtras.h"
namespace lld {
namespace macho {
-class InputSection;
class MachHeaderSection;
struct StringRefZ {
UndefinedKind,
CommonKind,
DylibKind,
- LazyKind,
+ LazyArchiveKind,
+ LazyObjectKind,
+ AliasKind,
};
virtual ~Symbol() {}
return {nameData, nameSize};
}
- bool isLive() const;
+ bool isLive() const { return used; }
+ bool isLazy() const {
+ return symbolKind == LazyArchiveKind || symbolKind == LazyObjectKind;
+ }
virtual uint64_t getVA() const { return 0; }
// Only undefined or dylib symbols can be weak references. A weak reference
// need not be satisfied at runtime, e.g. due to the symbol not being
// available on a given target platform.
- virtual bool isWeakRef() const { llvm_unreachable("cannot be a weak ref"); }
+ virtual bool isWeakRef() const { return false; }
virtual bool isTlv() const { llvm_unreachable("cannot be TLV"); }
bool isInStubs() const { return stubsIndex != UINT32_MAX; }
uint64_t getStubVA() const;
+ uint64_t getLazyPtrVA() const;
uint64_t getGotVA() const;
uint64_t getTlvVA() const;
uint64_t resolveBranchVA() const {
// on whether it is a thread-local. A given symbol cannot be referenced by
// both these sections at once.
uint32_t gotIndex = UINT32_MAX;
-
+ uint32_t lazyBindOffset = UINT32_MAX;
+ uint32_t stubsHelperIndex = UINT32_MAX;
uint32_t stubsIndex = UINT32_MAX;
-
uint32_t symtabIndex = UINT32_MAX;
InputFile *getFile() const { return file; }
protected:
Symbol(Kind k, StringRefZ name, InputFile *file)
- : symbolKind(k), nameData(name.data), nameSize(name.size), file(file),
+ : symbolKind(k), nameData(name.data), file(file), nameSize(name.size),
isUsedInRegularObj(!file || isa<ObjFile>(file)),
used(!config->deadStrip) {}
Kind symbolKind;
const char *nameData;
- mutable uint32_t nameSize;
InputFile *file;
+ mutable uint32_t nameSize;
public:
// True if this symbol was referenced by a regular (non-bitcode) object.
bool isUsedInRegularObj : 1;
- // True if an undefined or dylib symbol is used from a live section.
+ // True if this symbol is used from a live section.
bool used : 1;
};
public:
Defined(StringRefZ name, InputFile *file, InputSection *isec, uint64_t value,
uint64_t size, bool isWeakDef, bool isExternal, bool isPrivateExtern,
- bool isThumb, bool isReferencedDynamically, bool noDeadStrip)
- : Symbol(DefinedKind, name, file), isec(isec), value(value), size(size),
- overridesWeakDef(false), privateExtern(isPrivateExtern),
- includeInSymtab(true), thumb(isThumb),
- referencedDynamically(isReferencedDynamically),
- noDeadStrip(noDeadStrip), weakDef(isWeakDef), external(isExternal) {
- if (auto concatIsec = dyn_cast_or_null<ConcatInputSection>(isec))
- concatIsec->numRefs++;
- }
+ bool includeInSymtab, bool isThumb, bool isReferencedDynamically,
+ bool noDeadStrip, bool canOverrideWeakDef = false,
+ bool isWeakDefCanBeHidden = false, bool interposable = false);
bool isWeakDef() const override { return weakDef; }
bool isExternalWeakDef() const {
return isWeakDef() && isExternal() && !privateExtern;
}
- bool isTlv() const override {
- return !isAbsolute() && isThreadLocalVariables(isec->getFlags());
- }
+ bool isTlv() const override;
bool isExternal() const { return external; }
bool isAbsolute() const { return isec == nullptr; }
uint64_t getVA() const override;
- static bool classof(const Symbol *s) { return s->kind() == DefinedKind; }
+ // Returns the object file that this symbol was defined in. This value differs
+ // from `getFile()` if the symbol originated from a bitcode file.
+ ObjFile *getObjectFile() const;
- InputSection *isec;
- // Contains the offset from the containing subsection. Note that this is
- // different from nlist::n_value, which is the absolute address of the symbol.
- uint64_t value;
- // size is only calculated for regular (non-bitcode) symbols.
- uint64_t size;
+ std::string getSourceLocation();
+
+ // Ensure this symbol's pointers to InputSections point to their canonical
+ // copies.
+ void canonicalize();
+
+ static bool classof(const Symbol *s) { return s->kind() == DefinedKind; }
+ // Place the bitfields first so that they can get placed in the tail padding
+ // of the parent class, on platforms which support it.
bool overridesWeakDef : 1;
// Whether this symbol should appear in the output binary's export trie.
bool privateExtern : 1;
// Whether this symbol should appear in the output symbol table.
bool includeInSymtab : 1;
+ // Whether this symbol was folded into a different symbol during ICF.
+ bool wasIdenticalCodeFolded : 1;
// Only relevant when compiling for Thumb-supporting arm32 archs.
bool thumb : 1;
// Symbols marked referencedDynamically won't be removed from the output's
// metadata. This is information only for the static linker and not written
// to the output.
bool noDeadStrip : 1;
+ // Whether references to this symbol can be interposed at runtime to point to
+ // a different symbol definition (with the same name). For example, if both
+ // dylib A and B define an interposable symbol _foo, and we load A before B at
+ // runtime, then all references to _foo within dylib B will point to the
+ // definition in dylib A.
+ //
+ // Only extern symbols may be interposable.
+ bool interposable : 1;
+
+ bool weakDefCanBeHidden : 1;
private:
const bool weakDef : 1;
const bool external : 1;
+
+public:
+ InputSection *isec;
+ // Contains the offset from the containing subsection. Note that this is
+ // different from nlist::n_value, which is the absolute address of the symbol.
+ uint64_t value;
+ // size is only calculated for regular (non-bitcode) symbols.
+ uint64_t size;
+ // This can be a subsection of either __compact_unwind or __eh_frame.
+ ConcatInputSection *unwindEntry = nullptr;
};
// This enum does double-duty: as a symbol property, it indicates whether & how
class Undefined : public Symbol {
public:
- Undefined(StringRefZ name, InputFile *file, RefState refState)
- : Symbol(UndefinedKind, name, file), refState(refState) {
+ Undefined(StringRefZ name, InputFile *file, RefState refState,
+ bool wasBitcodeSymbol)
+ : Symbol(UndefinedKind, name, file), refState(refState),
+ wasBitcodeSymbol(wasBitcodeSymbol) {
assert(refState != RefState::Unreferenced);
}
static bool classof(const Symbol *s) { return s->kind() == UndefinedKind; }
RefState refState : 2;
+ bool wasBitcodeSymbol;
};
// On Unix, it is traditionally allowed to write variable definitions without
uint64_t getVA() const override;
bool isWeakDef() const override { return weakDef; }
- bool isWeakRef() const override { return refState == RefState::Weak; }
+
+ // Symbols from weak libraries/frameworks are also weakly-referenced.
+ bool isWeakRef() const override {
+ return refState == RefState::Weak ||
+ (file && getFile()->umbrella->forceWeakImport);
+ }
bool isReferenced() const { return refState != RefState::Unreferenced; }
bool isTlv() const override { return tlv; }
bool isDynamicLookup() const { return file == nullptr; }
static bool classof(const Symbol *s) { return s->kind() == DylibKind; }
- uint32_t stubsHelperIndex = UINT32_MAX;
- uint32_t lazyBindOffset = UINT32_MAX;
-
RefState getRefState() const { return refState; }
void reference(RefState newState) {
const bool tlv : 1;
};
-class LazySymbol : public Symbol {
+class LazyArchive : public Symbol {
public:
- LazySymbol(ArchiveFile *file, const llvm::object::Archive::Symbol &sym)
- : Symbol(LazyKind, sym.getName(), file), sym(sym) {}
+ LazyArchive(ArchiveFile *file, const llvm::object::Archive::Symbol &sym)
+ : Symbol(LazyArchiveKind, sym.getName(), file), sym(sym) {}
ArchiveFile *getFile() const { return cast<ArchiveFile>(file); }
void fetchArchiveMember();
- static bool classof(const Symbol *s) { return s->kind() == LazyKind; }
+ static bool classof(const Symbol *s) { return s->kind() == LazyArchiveKind; }
private:
const llvm::object::Archive::Symbol sym;
};
+// A defined symbol in an ObjFile/BitcodeFile surrounded by --start-lib and
+// --end-lib.
+class LazyObject : public Symbol {
+public:
+ LazyObject(InputFile &file, StringRef name)
+ : Symbol(LazyObjectKind, name, &file) {
+ isUsedInRegularObj = false;
+ }
+
+ static bool classof(const Symbol *s) { return s->kind() == LazyObjectKind; }
+};
+
+// Represents N_INDR symbols. Note that if we are given valid, linkable inputs,
+// then all AliasSymbol instances will be converted into one of the other Symbol
+// types after `createAliases()` runs.
+class AliasSymbol final : public Symbol {
+public:
+ AliasSymbol(InputFile *file, StringRef name, StringRef aliasedName,
+ bool isPrivateExtern)
+ : Symbol(AliasKind, name, file), privateExtern(isPrivateExtern),
+ aliasedName(aliasedName) {}
+
+ StringRef getAliasedName() const { return aliasedName; }
+
+ static bool classof(const Symbol *s) { return s->kind() == AliasKind; }
+
+ const bool privateExtern;
+
+private:
+ StringRef aliasedName;
+};
+
union SymbolUnion {
alignas(Defined) char a[sizeof(Defined)];
alignas(Undefined) char b[sizeof(Undefined)];
alignas(CommonSymbol) char c[sizeof(CommonSymbol)];
alignas(DylibSymbol) char d[sizeof(DylibSymbol)];
- alignas(LazySymbol) char e[sizeof(LazySymbol)];
+ alignas(LazyArchive) char e[sizeof(LazyArchive)];
+ alignas(LazyObject) char f[sizeof(LazyObject)];
+ alignas(AliasSymbol) char g[sizeof(AliasSymbol)];
};
template <typename T, typename... ArgT>
return sym;
}
+// Can a symbol's address only be resolved at runtime?
+inline bool needsBinding(const Symbol *sym) {
+ if (isa<DylibSymbol>(sym))
+ return true;
+ if (const auto *defined = dyn_cast<Defined>(sym))
+ return defined->isExternalWeakDef() || defined->interposable;
+ return false;
+}
} // namespace macho
std::string toString(const macho::Symbol &);
#include "SymbolTable.h"
#include "Symbols.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LEB128.h"
+#include "llvm/Support/Parallel.h"
#include "llvm/Support/Path.h"
-#include "llvm/Support/SHA256.h"
+#include "llvm/Support/xxhash.h"
#if defined(__APPLE__)
#include <sys/mman.h>
+
+#define COMMON_DIGEST_FOR_OPENSSL
+#include <CommonCrypto/CommonDigest.h>
+#else
+#include "llvm/Support/SHA256.h"
#endif
#ifdef LLVM_HAVE_LIBXAR
#include <fcntl.h>
+extern "C" {
#include <xar/xar.h>
+}
#endif
using namespace llvm;
using namespace lld;
using namespace lld::macho;
+// Reads `len` bytes at data and writes the 32-byte SHA256 checksum to `output`.
+static void sha256(const uint8_t *data, size_t len, uint8_t *output) {
+#if defined(__APPLE__)
+ // FIXME: Make LLVM's SHA256 faster and use it unconditionally. See PR56121
+ // for some notes on this.
+ CC_SHA256(data, len, output);
+#else
+ ArrayRef<uint8_t> block(data, len);
+ std::array<uint8_t, 32> hash = SHA256::hash(block);
+ static_assert(hash.size() == CodeSignatureSection::hashSize);
+ memcpy(output, hash.data(), hash.size());
+#endif
+}
+
InStruct macho::in;
std::vector<SyntheticSection *> macho::syntheticSections;
SyntheticSection::SyntheticSection(const char *segname, const char *name)
: OutputSection(SyntheticKind, name) {
std::tie(this->segname, this->name) = maybeRenameSection({segname, name});
- isec = make<ConcatInputSection>(segname, name);
+ isec = makeSyntheticInputSection(segname, name);
isec->parent = this;
syntheticSections.push_back(this);
}
if (config->outputType == MH_EXECUTE && !config->staticLink &&
target->cpuSubtype == CPU_SUBTYPE_X86_64_ALL &&
- config->platform() == PlatformKind::macOS &&
+ config->platform() == PLATFORM_MACOS &&
config->platformInfo.minimum >= VersionTuple(10, 5))
subtype |= CPU_SUBTYPE_LIB64;
return subtype;
}
+static bool hasWeakBinding() {
+ return config->emitChainedFixups ? in.chainedFixups->hasWeakBinding()
+ : in.weakBinding->hasEntry();
+}
+
+static bool hasNonWeakDefinition() {
+ return config->emitChainedFixups ? in.chainedFixups->hasNonWeakDefinition()
+ : in.weakBinding->hasNonWeakDefinition();
+}
+
void MachHeaderSection::writeTo(uint8_t *buf) const {
auto *hdr = reinterpret_cast<mach_header *>(buf);
hdr->magic = target->magic;
if (config->outputType == MH_DYLIB && config->applicationExtension)
hdr->flags |= MH_APP_EXTENSION_SAFE;
- if (in.exports->hasWeakSymbol || in.weakBinding->hasNonWeakDefinition())
+ if (in.exports->hasWeakSymbol || hasNonWeakDefinition())
hdr->flags |= MH_WEAK_DEFINES;
- if (in.exports->hasWeakSymbol || in.weakBinding->hasEntry())
+ if (in.exports->hasWeakSymbol || hasWeakBinding())
hdr->flags |= MH_BINDS_TO_WEAK;
for (const OutputSegment *seg : outputSegments) {
: LinkEditSection(segment_names::linkEdit, section_names::rebase) {}
namespace {
-struct Rebase {
- OutputSegment *segment = nullptr;
- uint64_t offset = 0;
- uint64_t consecutiveCount = 0;
+struct RebaseState {
+ uint64_t sequenceLength;
+ uint64_t skipLength;
};
} // namespace
-// Rebase opcodes allow us to describe a contiguous sequence of rebase location
-// using a single DO_REBASE opcode. To take advantage of it, we delay emitting
-// `DO_REBASE` until we have reached the end of a contiguous sequence.
-static void encodeDoRebase(Rebase &rebase, raw_svector_ostream &os) {
- assert(rebase.consecutiveCount != 0);
- if (rebase.consecutiveCount <= REBASE_IMMEDIATE_MASK) {
- os << static_cast<uint8_t>(REBASE_OPCODE_DO_REBASE_IMM_TIMES |
- rebase.consecutiveCount);
+static void emitIncrement(uint64_t incr, raw_svector_ostream &os) {
+ assert(incr != 0);
+
+ if ((incr >> target->p2WordSize) <= REBASE_IMMEDIATE_MASK &&
+ (incr % target->wordSize) == 0) {
+ os << static_cast<uint8_t>(REBASE_OPCODE_ADD_ADDR_IMM_SCALED |
+ (incr >> target->p2WordSize));
} else {
- os << static_cast<uint8_t>(REBASE_OPCODE_DO_REBASE_ULEB_TIMES);
- encodeULEB128(rebase.consecutiveCount, os);
+ os << static_cast<uint8_t>(REBASE_OPCODE_ADD_ADDR_ULEB);
+ encodeULEB128(incr, os);
}
- rebase.consecutiveCount = 0;
}
-static void encodeRebase(const OutputSection *osec, uint64_t outSecOff,
- Rebase &lastRebase, raw_svector_ostream &os) {
- OutputSegment *seg = osec->parent;
- uint64_t offset = osec->getSegmentOffset() + outSecOff;
- if (lastRebase.segment != seg || lastRebase.offset != offset) {
- if (lastRebase.consecutiveCount != 0)
- encodeDoRebase(lastRebase, os);
-
- if (lastRebase.segment != seg) {
- os << static_cast<uint8_t>(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB |
- seg->index);
- encodeULEB128(offset, os);
- lastRebase.segment = seg;
- lastRebase.offset = offset;
+static void flushRebase(const RebaseState &state, raw_svector_ostream &os) {
+ assert(state.sequenceLength > 0);
+
+ if (state.skipLength == target->wordSize) {
+ if (state.sequenceLength <= REBASE_IMMEDIATE_MASK) {
+ os << static_cast<uint8_t>(REBASE_OPCODE_DO_REBASE_IMM_TIMES |
+ state.sequenceLength);
} else {
- assert(lastRebase.offset != offset);
- os << static_cast<uint8_t>(REBASE_OPCODE_ADD_ADDR_ULEB);
- encodeULEB128(offset - lastRebase.offset, os);
- lastRebase.offset = offset;
+ os << static_cast<uint8_t>(REBASE_OPCODE_DO_REBASE_ULEB_TIMES);
+ encodeULEB128(state.sequenceLength, os);
}
+ } else if (state.sequenceLength == 1) {
+ os << static_cast<uint8_t>(REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB);
+ encodeULEB128(state.skipLength - target->wordSize, os);
+ } else {
+ os << static_cast<uint8_t>(
+ REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB);
+ encodeULEB128(state.sequenceLength, os);
+ encodeULEB128(state.skipLength - target->wordSize, os);
}
- ++lastRebase.consecutiveCount;
- // DO_REBASE causes dyld to both perform the binding and increment the offset
- lastRebase.offset += target->wordSize;
+}
+
+// Rebases are communicated to dyld using a bytecode, whose opcodes cause the
+// memory location at a specific address to be rebased and/or the address to be
+// incremented.
+//
+// Opcode REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB is the most generic
+// one, encoding a series of evenly spaced addresses. This algorithm works by
+// splitting up the sorted list of addresses into such chunks. If the locations
+// are consecutive or the sequence consists of a single location, flushRebase
+// will use a smaller, more specialized encoding.
+static void encodeRebases(const OutputSegment *seg,
+ MutableArrayRef<Location> locations,
+ raw_svector_ostream &os) {
+ // dyld operates on segments. Translate section offsets into segment offsets.
+ for (Location &loc : locations)
+ loc.offset =
+ loc.isec->parent->getSegmentOffset() + loc.isec->getOffset(loc.offset);
+ // The algorithm assumes that locations are unique.
+ Location *end =
+ llvm::unique(locations, [](const Location &a, const Location &b) {
+ return a.offset == b.offset;
+ });
+ size_t count = end - locations.begin();
+
+ os << static_cast<uint8_t>(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB |
+ seg->index);
+ assert(!locations.empty());
+ uint64_t offset = locations[0].offset;
+ encodeULEB128(offset, os);
+
+ RebaseState state{1, target->wordSize};
+
+ for (size_t i = 1; i < count; ++i) {
+ offset = locations[i].offset;
+
+ uint64_t skip = offset - locations[i - 1].offset;
+ assert(skip != 0 && "duplicate locations should have been weeded out");
+
+ if (skip == state.skipLength) {
+ ++state.sequenceLength;
+ } else if (state.sequenceLength == 1) {
+ ++state.sequenceLength;
+ state.skipLength = skip;
+ } else if (skip < state.skipLength) {
+ // The address is lower than what the rebase pointer would be if the last
+ // location would be part of a sequence. We start a new sequence from the
+ // previous location.
+ --state.sequenceLength;
+ flushRebase(state, os);
+
+ state.sequenceLength = 2;
+ state.skipLength = skip;
+ } else {
+ // The address is at some positive offset from the rebase pointer. We
+ // start a new sequence which begins with the current location.
+ flushRebase(state, os);
+ emitIncrement(skip - state.skipLength, os);
+ state.sequenceLength = 1;
+ state.skipLength = target->wordSize;
+ }
+ }
+ flushRebase(state, os);
}
void RebaseSection::finalizeContents() {
return;
raw_svector_ostream os{contents};
- Rebase lastRebase;
-
os << static_cast<uint8_t>(REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER);
llvm::sort(locations, [](const Location &a, const Location &b) {
return a.isec->getVA(a.offset) < b.isec->getVA(b.offset);
});
- for (const Location &loc : locations)
- encodeRebase(loc.isec->parent, loc.isec->getOffset(loc.offset), lastRebase,
- os);
- if (lastRebase.consecutiveCount != 0)
- encodeDoRebase(lastRebase, os);
+ for (size_t i = 0, count = locations.size(); i < count;) {
+ const OutputSegment *seg = locations[i].isec->parent->parent;
+ size_t j = i + 1;
+ while (j < count && locations[j].isec->parent->parent == seg)
+ ++j;
+ encodeRebases(seg, {locations.data() + i, locations.data() + j}, os);
+ i = j;
+ }
os << static_cast<uint8_t>(REBASE_OPCODE_DONE);
}
void macho::addNonLazyBindingEntries(const Symbol *sym,
const InputSection *isec, uint64_t offset,
int64_t addend) {
+ if (config->emitChainedFixups) {
+ if (needsBinding(sym))
+ in.chainedFixups->addBinding(sym, isec, offset, addend);
+ else if (isa<Defined>(sym))
+ in.chainedFixups->addRebase(isec, offset);
+ else
+ llvm_unreachable("cannot bind to an undefined symbol");
+ return;
+ }
+
if (const auto *dysym = dyn_cast<DylibSymbol>(sym)) {
in.binding->addEntry(dysym, isec, offset, addend);
if (dysym->isWeakDef())
in.rebase->addEntry(isec, offset);
if (defined->isExternalWeakDef())
in.weakBinding->addEntry(sym, isec, offset, addend);
+ else if (defined->interposable)
+ in.binding->addEntry(sym, isec, offset, addend);
} else {
// Undefined symbols are filtered out in scanRelocations(); we should never
// get here
}
}
+void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA) {
+ assert(config->emitChainedFixups);
+ assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
+ auto *rebase = reinterpret_cast<dyld_chained_ptr_64_rebase *>(buf);
+ rebase->target = targetVA & 0xf'ffff'ffff;
+ rebase->high8 = (targetVA >> 56);
+ rebase->reserved = 0;
+ rebase->next = 0;
+ rebase->bind = 0;
+
+ // The fixup format places a 64 GiB limit on the output's size.
+ // Should we handle this gracefully?
+ uint64_t encodedVA = rebase->target | ((uint64_t)rebase->high8 << 56);
+ if (encodedVA != targetVA)
+ error("rebase target address 0x" + Twine::utohexstr(targetVA) +
+ " does not fit into chained fixup. Re-link with -no_fixup_chains");
+}
+
+static void writeChainedBind(uint8_t *buf, const Symbol *sym, int64_t addend) {
+ assert(config->emitChainedFixups);
+ assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
+ auto *bind = reinterpret_cast<dyld_chained_ptr_64_bind *>(buf);
+ auto [ordinal, inlineAddend] = in.chainedFixups->getBinding(sym, addend);
+ bind->ordinal = ordinal;
+ bind->addend = inlineAddend;
+ bind->reserved = 0;
+ bind->next = 0;
+ bind->bind = 1;
+}
+
+void macho::writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend) {
+ if (needsBinding(sym))
+ writeChainedBind(buf, sym, addend);
+ else
+ writeChainedRebase(buf, sym->getVA() + addend);
+}
+
void NonLazyPointerSectionBase::writeTo(uint8_t *buf) const {
- for (size_t i = 0, n = entries.size(); i < n; ++i)
- if (auto *defined = dyn_cast<Defined>(entries[i]))
- write64le(&buf[i * target->wordSize], defined->getVA());
+ if (config->emitChainedFixups) {
+ for (const auto &[i, entry] : llvm::enumerate(entries))
+ writeChainedFixup(&buf[i * target->wordSize], entry, 0);
+ } else {
+ for (const auto &[i, entry] : llvm::enumerate(entries))
+ if (auto *defined = dyn_cast<Defined>(entry))
+ write64le(&buf[i * target->wordSize], defined->getVA());
+ }
}
GotSection::GotSection()
- : NonLazyPointerSectionBase(segment_names::dataConst, section_names::got) {
+ : NonLazyPointerSectionBase(segment_names::data, section_names::got) {
flags = S_NON_LAZY_SYMBOL_POINTERS;
}
return dysym.getFile()->ordinal;
}
+static int16_t ordinalForSymbol(const Symbol &sym) {
+ if (const auto *dysym = dyn_cast<DylibSymbol>(&sym))
+ return ordinalForDylibSymbol(*dysym);
+ assert(cast<Defined>(&sym)->interposable);
+ return BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
+}
+
static void encodeDylibOrdinal(int16_t ordinal, raw_svector_ostream &os) {
if (ordinal <= 0) {
os << static_cast<uint8_t>(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM |
int16_t lastOrdinal = 0;
for (auto &p : sortBindings(bindingsMap)) {
- const DylibSymbol *sym = p.first;
+ const Symbol *sym = p.first;
std::vector<BindingEntry> &bindings = p.second;
uint8_t flags = BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM;
if (sym->isWeakRef())
flags |= BIND_SYMBOL_FLAGS_WEAK_IMPORT;
os << flags << sym->getName() << '\0'
<< static_cast<uint8_t>(BIND_OPCODE_SET_TYPE_IMM | BIND_TYPE_POINTER);
- int16_t ordinal = ordinalForDylibSymbol(*sym);
+ int16_t ordinal = ordinalForSymbol(*sym);
if (ordinal != lastOrdinal) {
encodeDylibOrdinal(ordinal, os);
lastOrdinal = ordinal;
void StubsSection::writeTo(uint8_t *buf) const {
size_t off = 0;
for (const Symbol *sym : entries) {
- target->writeStub(buf + off, *sym);
+ uint64_t pointerVA =
+ config->emitChainedFixups ? sym->getGotVA() : sym->getLazyPtrVA();
+ target->writeStub(buf + off, *sym, pointerVA);
off += target->stubSize;
}
}
void StubsSection::finalize() { isFinal = true; }
-bool StubsSection::addEntry(Symbol *sym) {
+static void addBindingsForStub(Symbol *sym) {
+ assert(!config->emitChainedFixups);
+ if (auto *dysym = dyn_cast<DylibSymbol>(sym)) {
+ if (sym->isWeakDef()) {
+ in.binding->addEntry(dysym, in.lazyPointers->isec,
+ sym->stubsIndex * target->wordSize);
+ in.weakBinding->addEntry(sym, in.lazyPointers->isec,
+ sym->stubsIndex * target->wordSize);
+ } else {
+ in.lazyBinding->addEntry(dysym);
+ }
+ } else if (auto *defined = dyn_cast<Defined>(sym)) {
+ if (defined->isExternalWeakDef()) {
+ in.rebase->addEntry(in.lazyPointers->isec,
+ sym->stubsIndex * target->wordSize);
+ in.weakBinding->addEntry(sym, in.lazyPointers->isec,
+ sym->stubsIndex * target->wordSize);
+ } else if (defined->interposable) {
+ in.lazyBinding->addEntry(sym);
+ } else {
+ llvm_unreachable("invalid stub target");
+ }
+ } else {
+ llvm_unreachable("invalid stub target symbol type");
+ }
+}
+
+void StubsSection::addEntry(Symbol *sym) {
bool inserted = entries.insert(sym);
- if (inserted)
+ if (inserted) {
sym->stubsIndex = entries.size() - 1;
- return inserted;
+
+ if (config->emitChainedFixups)
+ in.got->addEntry(sym);
+ else
+ addBindingsForStub(sym);
+ }
}
StubHelperSection::StubHelperSection()
void StubHelperSection::writeTo(uint8_t *buf) const {
target->writeStubHelperHeader(buf);
size_t off = target->stubHelperHeaderSize;
- for (const DylibSymbol *sym : in.lazyBinding->getEntries()) {
+ for (const Symbol *sym : in.lazyBinding->getEntries()) {
target->writeStubHelperEntry(buf + off, *sym, addr + off);
off += target->stubHelperEntrySize;
}
}
-void StubHelperSection::setup() {
+void StubHelperSection::setUp() {
Symbol *binder = symtab->addUndefined("dyld_stub_binder", /*file=*/nullptr,
/*isWeakRef=*/false);
if (auto *undefined = dyn_cast<Undefined>(binder))
ConcatOutputSection::getOrCreateForInput(in.imageLoaderCache);
inputSections.push_back(in.imageLoaderCache);
// Since this isn't in the symbol table or in any input file, the noDeadStrip
- // argument doesn't matter. It's kept alive by ImageLoaderCacheSection()
- // setting `live` to true on the backing InputSection.
+ // argument doesn't matter.
dyldPrivate =
make<Defined>("__dyld_private", nullptr, in.imageLoaderCache, 0, 0,
/*isWeakDef=*/false,
/*isExternal=*/false, /*isPrivateExtern=*/false,
+ /*includeInSymtab=*/true,
/*isThumb=*/false, /*isReferencedDynamically=*/false,
/*noDeadStrip=*/false);
+ dyldPrivate->used = true;
+}
+
+ObjCStubsSection::ObjCStubsSection()
+ : SyntheticSection(segment_names::text, section_names::objcStubs) {
+ flags = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
+ align = target->objcStubsAlignment;
+}
+
+void ObjCStubsSection::addEntry(Symbol *sym) {
+ assert(sym->getName().startswith(symbolPrefix) && "not an objc stub");
+ StringRef methname = sym->getName().drop_front(symbolPrefix.size());
+ offsets.push_back(
+ in.objcMethnameSection->getStringOffset(methname).outSecOff);
+ Defined *newSym = replaceSymbol<Defined>(
+ sym, sym->getName(), nullptr, isec,
+ /*value=*/symbols.size() * target->objcStubsFastSize,
+ /*size=*/target->objcStubsFastSize,
+ /*isWeakDef=*/false, /*isExternal=*/true, /*isPrivateExtern=*/true,
+ /*includeInSymtab=*/true, /*isThumb=*/false,
+ /*isReferencedDynamically=*/false, /*noDeadStrip=*/false);
+ symbols.push_back(newSym);
+}
+
+void ObjCStubsSection::setUp() {
+ Symbol *objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr,
+ /*isWeakRef=*/false);
+ objcMsgSend->used = true;
+ in.got->addEntry(objcMsgSend);
+ assert(objcMsgSend->isInGot());
+ objcMsgSendGotIndex = objcMsgSend->gotIndex;
+
+ size_t size = offsets.size() * target->wordSize;
+ uint8_t *selrefsData = bAlloc().Allocate<uint8_t>(size);
+ for (size_t i = 0, n = offsets.size(); i < n; ++i)
+ write64le(&selrefsData[i * target->wordSize], offsets[i]);
+
+ in.objcSelrefs =
+ makeSyntheticInputSection(segment_names::data, section_names::objcSelrefs,
+ S_LITERAL_POINTERS | S_ATTR_NO_DEAD_STRIP,
+ ArrayRef<uint8_t>{selrefsData, size},
+ /*align=*/target->wordSize);
+ in.objcSelrefs->live = true;
+
+ for (size_t i = 0, n = offsets.size(); i < n; ++i) {
+ in.objcSelrefs->relocs.push_back(
+ {/*type=*/target->unsignedRelocType,
+ /*pcrel=*/false, /*length=*/3,
+ /*offset=*/static_cast<uint32_t>(i * target->wordSize),
+ /*addend=*/offsets[i] * in.objcMethnameSection->align,
+ /*referent=*/in.objcMethnameSection->isec});
+ }
+
+ in.objcSelrefs->parent =
+ ConcatOutputSection::getOrCreateForInput(in.objcSelrefs);
+ inputSections.push_back(in.objcSelrefs);
+ in.objcSelrefs->isFinal = true;
+}
+
+uint64_t ObjCStubsSection::getSize() const {
+ return target->objcStubsFastSize * symbols.size();
+}
+
+void ObjCStubsSection::writeTo(uint8_t *buf) const {
+ assert(in.objcSelrefs->live);
+ assert(in.objcSelrefs->isFinal);
+
+ uint64_t stubOffset = 0;
+ for (size_t i = 0, n = symbols.size(); i < n; ++i) {
+ Defined *sym = symbols[i];
+ target->writeObjCMsgSendStub(buf + stubOffset, sym, in.objcStubs->addr,
+ stubOffset, in.objcSelrefs->getVA(), i,
+ in.got->addr, objcMsgSendGotIndex);
+ stubOffset += target->objcStubsFastSize;
+ }
}
LazyPointerSection::LazyPointerSection()
void LazyBindingSection::finalizeContents() {
// TODO: Just precompute output size here instead of writing to a temporary
// buffer
- for (DylibSymbol *sym : entries)
+ for (Symbol *sym : entries)
sym->lazyBindOffset = encode(*sym);
}
memcpy(buf, contents.data(), contents.size());
}
-void LazyBindingSection::addEntry(DylibSymbol *dysym) {
- if (entries.insert(dysym)) {
- dysym->stubsHelperIndex = entries.size() - 1;
+void LazyBindingSection::addEntry(Symbol *sym) {
+ assert(!config->emitChainedFixups && "Chained fixups always bind eagerly");
+ if (entries.insert(sym)) {
+ sym->stubsHelperIndex = entries.size() - 1;
in.rebase->addEntry(in.lazyPointers->isec,
- dysym->stubsIndex * target->wordSize);
+ sym->stubsIndex * target->wordSize);
}
}
// BIND_OPCODE_DONE terminator. As such, unlike in the non-lazy-binding case,
// we cannot encode just the differences between symbols; we have to emit the
// complete bind information for each symbol.
-uint32_t LazyBindingSection::encode(const DylibSymbol &sym) {
+uint32_t LazyBindingSection::encode(const Symbol &sym) {
uint32_t opstreamOffset = contents.size();
OutputSegment *dataSeg = in.lazyPointers->parent;
os << static_cast<uint8_t>(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB |
dataSeg->index);
- uint64_t offset = in.lazyPointers->addr - dataSeg->addr +
- sym.stubsIndex * target->wordSize;
+ uint64_t offset =
+ in.lazyPointers->addr - dataSeg->addr + sym.stubsIndex * target->wordSize;
encodeULEB128(offset, os);
- encodeDylibOrdinal(ordinalForDylibSymbol(sym), os);
+ encodeDylibOrdinal(ordinalForSymbol(sym), os);
uint8_t flags = BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM;
if (sym.isWeakRef())
template <class LP>
static std::vector<MachO::data_in_code_entry> collectDataInCodeEntries() {
- using SegmentCommand = typename LP::segment_command;
- using Section = typename LP::section;
-
std::vector<MachO::data_in_code_entry> dataInCodeEntries;
for (const InputFile *inputFile : inputFiles) {
if (!isa<ObjFile>(inputFile))
continue;
const ObjFile *objFile = cast<ObjFile>(inputFile);
- const auto *c = reinterpret_cast<const SegmentCommand *>(
- findCommand(objFile->mb.getBufferStart(), LP::segmentLCType));
- if (!c)
- continue;
- ArrayRef<Section> sections{reinterpret_cast<const Section *>(c + 1),
- c->nsects};
-
- ArrayRef<MachO::data_in_code_entry> entries = objFile->dataInCodeEntries;
+ ArrayRef<MachO::data_in_code_entry> entries = objFile->getDataInCode();
if (entries.empty())
continue;
+
+ assert(is_sorted(entries, [](const data_in_code_entry &lhs,
+ const data_in_code_entry &rhs) {
+ return lhs.offset < rhs.offset;
+ }));
// For each code subsection find 'data in code' entries residing in it.
// Compute the new offset values as
// <offset within subsection> + <subsection address> - <__TEXT address>.
- for (size_t i = 0, n = sections.size(); i < n; ++i) {
- const SubsectionMap &subsecMap = objFile->subsections[i];
- for (const SubsectionEntry &subsecEntry : subsecMap) {
- const InputSection *isec = subsecEntry.isec;
+ for (const Section *section : objFile->sections) {
+ for (const Subsection &subsec : section->subsections) {
+ const InputSection *isec = subsec.isec;
if (!isCodeSection(isec))
continue;
if (cast<ConcatInputSection>(isec)->shouldOmitFromOutput())
continue;
- const uint64_t beginAddr = sections[i].addr + subsecEntry.offset;
+ const uint64_t beginAddr = section->addr + subsec.offset;
auto it = llvm::lower_bound(
entries, beginAddr,
[](const MachO::data_in_code_entry &entry, uint64_t addr) {
return entry.offset < addr;
});
- const uint64_t endAddr = beginAddr + isec->getFileSize();
+ const uint64_t endAddr = beginAddr + isec->getSize();
for (const auto end = entries.end();
it != end && it->offset + it->length <= endAddr; ++it)
dataInCodeEntries.push_back(
}
}
}
+
+ // ld64 emits the table in sorted order too.
+ llvm::sort(dataInCodeEntries,
+ [](const data_in_code_entry &lhs, const data_in_code_entry &rhs) {
+ return lhs.offset < rhs.offset;
+ });
return dataInCodeEntries;
}
void FunctionStartsSection::finalizeContents() {
raw_svector_ostream os{contents};
std::vector<uint64_t> addrs;
- for (const Symbol *sym : symtab->getSymbols()) {
- if (const auto *defined = dyn_cast<Defined>(sym)) {
- if (!defined->isec || !isCodeSection(defined->isec) || !defined->isLive())
- continue;
- if (const auto *concatIsec = dyn_cast<ConcatInputSection>(defined->isec))
- if (concatIsec->shouldOmitFromOutput())
- continue;
- // TODO: Add support for thumbs, in that case
- // the lowest bit of nextAddr needs to be set to 1.
- addrs.push_back(defined->getVA());
+ for (const InputFile *file : inputFiles) {
+ if (auto *objFile = dyn_cast<ObjFile>(file)) {
+ for (const Symbol *sym : objFile->symbols) {
+ if (const auto *defined = dyn_cast_or_null<Defined>(sym)) {
+ if (!defined->isec || !isCodeSection(defined->isec) ||
+ !defined->isLive())
+ continue;
+ // TODO: Add support for thumbs, in that case
+ // the lowest bit of nextAddr needs to be set to 1.
+ addrs.push_back(defined->getVA());
+ }
+ }
}
}
llvm::sort(addrs);
: LinkEditSection(segment_names::linkEdit, section_names::symbolTable),
stringTableSection(stringTableSection) {}
-void SymtabSection::emitBeginSourceStab(DWARFUnit *compileUnit) {
+void SymtabSection::emitBeginSourceStab(StringRef sourceFile) {
StabsEntry stab(N_SO);
- SmallString<261> dir(compileUnit->getCompilationDir());
- StringRef sep = sys::path::get_separator();
- // We don't use `path::append` here because we want an empty `dir` to result
- // in an absolute path. `append` would give us a relative path for that case.
- if (!dir.endswith(sep))
- dir += sep;
- stab.strx = stringTableSection.addString(
- saver.save(dir + compileUnit->getUnitDIE().getShortName()));
+ stab.strx = stringTableSection.addString(saver().save(sourceFile));
stabs.emplace_back(std::move(stab));
}
if (!file->archiveName.empty())
path.append({"(", file->getName(), ")"});
- stab.strx = stringTableSection.addString(saver.save(path.str()));
+ StringRef adjustedPath = saver().save(path.str());
+ adjustedPath.consume_front(config->osoPrefix);
+
+ stab.strx = stringTableSection.addString(adjustedPath);
stab.desc = 1;
stab.value = file->modTime;
stabs.emplace_back(std::move(stab));
}
void SymtabSection::emitStabs() {
+ if (config->omitDebugInfo)
+ return;
+
for (const std::string &s : config->astPaths) {
StabsEntry astStab(N_AST);
astStab.strx = stringTableSection.addString(s);
stabs.emplace_back(std::move(astStab));
}
- std::vector<Defined *> symbolsNeedingStabs;
+ // Cache the file ID for each symbol in an std::pair for faster sorting.
+ using SortingPair = std::pair<Defined *, int>;
+ std::vector<SortingPair> symbolsNeedingStabs;
for (const SymtabEntry &entry :
concat<SymtabEntry>(localSymbols, externalSymbols)) {
Symbol *sym = entry.sym;
assert(sym->isLive() &&
"dead symbols should not be in localSymbols, externalSymbols");
if (auto *defined = dyn_cast<Defined>(sym)) {
+ // Excluded symbols should have been filtered out in finalizeContents().
+ assert(defined->includeInSymtab);
+
if (defined->isAbsolute())
continue;
- InputSection *isec = defined->isec;
- ObjFile *file = dyn_cast_or_null<ObjFile>(isec->getFile());
+
+ // Constant-folded symbols go in the executable's symbol table, but don't
+ // get a stabs entry.
+ if (defined->wasIdenticalCodeFolded)
+ continue;
+
+ ObjFile *file = defined->getObjectFile();
if (!file || !file->compileUnit)
continue;
- symbolsNeedingStabs.push_back(defined);
+
+ symbolsNeedingStabs.emplace_back(defined, defined->isec->getFile()->id);
}
}
- llvm::stable_sort(symbolsNeedingStabs, [&](Defined *a, Defined *b) {
- return a->isec->getFile()->id < b->isec->getFile()->id;
- });
+ llvm::stable_sort(symbolsNeedingStabs,
+ [&](const SortingPair &a, const SortingPair &b) {
+ return a.second < b.second;
+ });
// Emit STABS symbols so that dsymutil and/or the debugger can map address
// regions in the final binary to the source and object files from which they
// originated.
InputFile *lastFile = nullptr;
- for (Defined *defined : symbolsNeedingStabs) {
+ for (SortingPair &pair : symbolsNeedingStabs) {
+ Defined *defined = pair.first;
InputSection *isec = defined->isec;
ObjFile *file = cast<ObjFile>(isec->getFile());
emitEndSourceStab();
lastFile = file;
- emitBeginSourceStab(file->compileUnit);
+ emitBeginSourceStab(file->sourceFile());
emitObjectFileStab(file);
}
StabsEntry symStab;
- symStab.sect = defined->isec->canonical()->parent->index;
+ symStab.sect = defined->isec->parent->index;
symStab.strx = stringTableSection.addString(defined->getName());
symStab.value = defined->getVA();
symbols.push_back({sym, strx});
};
+ std::function<void(Symbol *)> localSymbolsHandler;
+ switch (config->localSymbolsPresence) {
+ case SymtabPresence::All:
+ localSymbolsHandler = [&](Symbol *sym) { addSymbol(localSymbols, sym); };
+ break;
+ case SymtabPresence::None:
+ localSymbolsHandler = [&](Symbol *) { /* Do nothing*/ };
+ break;
+ case SymtabPresence::SelectivelyIncluded:
+ localSymbolsHandler = [&](Symbol *sym) {
+ if (config->localSymbolPatterns.match(sym->getName()))
+ addSymbol(localSymbols, sym);
+ };
+ break;
+ case SymtabPresence::SelectivelyExcluded:
+ localSymbolsHandler = [&](Symbol *sym) {
+ if (!config->localSymbolPatterns.match(sym->getName()))
+ addSymbol(localSymbols, sym);
+ };
+ break;
+ }
+
// Local symbols aren't in the SymbolTable, so we walk the list of object
// files to gather them.
- for (const InputFile *file : inputFiles) {
- if (auto *objFile = dyn_cast<ObjFile>(file)) {
- for (Symbol *sym : objFile->symbols) {
- if (auto *defined = dyn_cast_or_null<Defined>(sym)) {
- if (!defined->isExternal() && defined->isLive()) {
- StringRef name = defined->getName();
- if (!name.startswith("l") && !name.startswith("L"))
- addSymbol(localSymbols, sym);
+ // But if `-x` is set, then we don't need to. localSymbolsHandler() will do
+ // the right thing regardless, but this check is a perf optimization because
+ // iterating through all the input files and their symbols is expensive.
+ if (config->localSymbolsPresence != SymtabPresence::None) {
+ for (const InputFile *file : inputFiles) {
+ if (auto *objFile = dyn_cast<ObjFile>(file)) {
+ for (Symbol *sym : objFile->symbols) {
+ if (auto *defined = dyn_cast_or_null<Defined>(sym)) {
+ if (defined->isExternal() || !defined->isLive() ||
+ !defined->includeInSymtab)
+ continue;
+ localSymbolsHandler(sym);
}
}
}
// __dyld_private is a local symbol too. It's linker-created and doesn't
// exist in any object file.
- if (Defined *dyldPrivate = in.stubHelper->dyldPrivate)
- addSymbol(localSymbols, dyldPrivate);
+ if (in.stubHelper && in.stubHelper->dyldPrivate)
+ localSymbolsHandler(in.stubHelper->dyldPrivate);
for (Symbol *sym : symtab->getSymbols()) {
if (!sym->isLive())
continue;
assert(defined->isExternal());
if (defined->privateExtern)
- addSymbol(localSymbols, defined);
+ localSymbolsHandler(defined);
else
addSymbol(externalSymbols, defined);
} else if (auto *dysym = dyn_cast<DylibSymbol>(sym)) {
nList->n_value = defined->value;
} else {
nList->n_type = scope | N_SECT;
- nList->n_sect = defined->isec->canonical()->parent->index;
+ nList->n_sect = defined->isec->parent->index;
// For the N_SECT symbol type, n_value is the address of the symbol
nList->n_value = defined->getVA();
}
section_names::indirectSymbolTable) {}
uint32_t IndirectSymtabSection::getNumSymbols() const {
- return in.got->getEntries().size() + in.tlvPointers->getEntries().size() +
- 2 * in.stubs->getEntries().size();
+ uint32_t size = in.got->getEntries().size() +
+ in.tlvPointers->getEntries().size() +
+ in.stubs->getEntries().size();
+ if (!config->emitChainedFixups)
+ size += in.stubs->getEntries().size();
+ return size;
}
bool IndirectSymtabSection::isNeeded() const {
in.tlvPointers->reserved1 = off;
off += in.tlvPointers->getEntries().size();
in.stubs->reserved1 = off;
- off += in.stubs->getEntries().size();
- in.lazyPointers->reserved1 = off;
+ if (in.lazyPointers) {
+ off += in.stubs->getEntries().size();
+ in.lazyPointers->reserved1 = off;
+ }
}
static uint32_t indirectValue(const Symbol *sym) {
- return sym->symtabIndex != UINT32_MAX ? sym->symtabIndex
- : INDIRECT_SYMBOL_LOCAL;
+ if (sym->symtabIndex == UINT32_MAX)
+ return INDIRECT_SYMBOL_LOCAL;
+ if (auto *defined = dyn_cast<Defined>(sym))
+ if (defined->privateExtern)
+ return INDIRECT_SYMBOL_LOCAL;
+ return sym->symtabIndex;
}
void IndirectSymtabSection::writeTo(uint8_t *buf) const {
write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
++off;
}
- // There is a 1:1 correspondence between stubs and LazyPointerSection
- // entries. But giving __stubs and __la_symbol_ptr the same reserved1
- // (the offset into the indirect symbol table) so that they both refer
- // to the same range of offsets confuses `strip`, so write the stubs
- // symbol table offsets a second time.
- for (const Symbol *sym : in.stubs->getEntries()) {
- write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
- ++off;
+
+ if (in.lazyPointers) {
+ // There is a 1:1 correspondence between stubs and LazyPointerSection
+ // entries. But giving __stubs and __la_symbol_ptr the same reserved1
+ // (the offset into the indirect symbol table) so that they both refer
+ // to the same range of offsets confuses `strip`, so write the stubs
+ // symbol table offsets a second time.
+ for (const Symbol *sym : in.stubs->getEntries()) {
+ write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
+ ++off;
+ }
}
}
}
}
-static_assert((CodeSignatureSection::blobHeadersSize % 8) == 0, "");
-static_assert((CodeSignatureSection::fixedHeadersSize % 8) == 0, "");
+static_assert((CodeSignatureSection::blobHeadersSize % 8) == 0);
+static_assert((CodeSignatureSection::fixedHeadersSize % 8) == 0);
CodeSignatureSection::CodeSignatureSection()
: LinkEditSection(segment_names::linkEdit, section_names::codeSignature) {
size_t slashIndex = fileName.rfind("/");
if (slashIndex != std::string::npos)
fileName = fileName.drop_front(slashIndex + 1);
+
+ // NOTE: Any changes to these calculations should be repeated
+ // in llvm-objcopy's MachOLayoutBuilder::layoutTail.
allHeadersSize = alignTo<16>(fixedHeadersSize + fileName.size() + 1);
fileNamePad = allHeadersSize - fixedHeadersSize - fileName.size();
}
}
void CodeSignatureSection::writeHashes(uint8_t *buf) const {
- uint8_t *code = buf;
- uint8_t *codeEnd = buf + fileOff;
- uint8_t *hashes = codeEnd + allHeadersSize;
- while (code < codeEnd) {
- StringRef block(reinterpret_cast<char *>(code),
- std::min(codeEnd - code, static_cast<ssize_t>(blockSize)));
- SHA256 hasher;
- hasher.update(block);
- StringRef hash = hasher.final();
- assert(hash.size() == hashSize);
- memcpy(hashes, hash.data(), hashSize);
- code += blockSize;
- hashes += hashSize;
- }
+ // NOTE: Changes to this functionality should be repeated in llvm-objcopy's
+ // MachOWriter::writeSignatureData.
+ uint8_t *hashes = buf + fileOff + allHeadersSize;
+ parallelFor(0, getBlockCount(), [&](size_t i) {
+ sha256(buf + i * blockSize,
+ std::min(static_cast<size_t>(fileOff - i * blockSize), blockSize),
+ hashes + i * hashSize);
+ });
#if defined(__APPLE__)
// This is macOS-specific work-around and makes no sense for any
// other host OS. See https://openradar.appspot.com/FB8914231
}
void CodeSignatureSection::writeTo(uint8_t *buf) const {
+ // NOTE: Changes to this functionality should be repeated in llvm-objcopy's
+ // MachOWriter::writeSignatureData.
uint32_t signatureSize = static_cast<uint32_t>(getSize());
auto *superBlob = reinterpret_cast<CS_SuperBlob *>(buf);
write32be(&superBlob->magic, CSMAGIC_EMBEDDED_SIGNATURE);
using namespace llvm::sys::fs;
CHECK_EC(createTemporaryFile("bitcode-bundle", "xar", xarPath));
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
xar_t xar(xar_open(xarPath.data(), O_RDWR));
+#pragma clang diagnostic pop
if (!xar)
fatal("failed to open XAR temporary file at " + xarPath);
CHECK_EC(xar_opt_set(xar, XAR_OPT_COMPRESSION, XAR_OPT_VAL_NONE));
remove(xarPath);
}
-CStringSection::CStringSection()
- : SyntheticSection(segment_names::text, section_names::cString) {
+CStringSection::CStringSection(const char *name)
+ : SyntheticSection(segment_names::text, name) {
flags = S_CSTRING_LITERALS;
}
void CStringSection::writeTo(uint8_t *buf) const {
for (const CStringInputSection *isec : inputs) {
- for (size_t i = 0, e = isec->pieces.size(); i != e; ++i) {
- if (!isec->pieces[i].live)
+ for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+ if (!piece.live)
continue;
StringRef string = isec->getStringRef(i);
- memcpy(buf + isec->pieces[i].outSecOff, string.data(), string.size());
+ memcpy(buf + piece.outSecOff, string.data(), string.size());
}
}
}
void CStringSection::finalizeContents() {
uint64_t offset = 0;
for (CStringInputSection *isec : inputs) {
- for (size_t i = 0, e = isec->pieces.size(); i != e; ++i) {
- if (!isec->pieces[i].live)
+ for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+ if (!piece.live)
continue;
- uint32_t pieceAlign = MinAlign(isec->pieces[i].inSecOff, align);
+ // See comment above DeduplicatedCStringSection for how alignment is
+ // handled.
+ uint32_t pieceAlign = 1
+ << countTrailingZeros(isec->align | piece.inSecOff);
offset = alignTo(offset, pieceAlign);
- isec->pieces[i].outSecOff = offset;
+ piece.outSecOff = offset;
isec->isFinal = true;
StringRef string = isec->getStringRef(i);
- offset += string.size();
+ offset += string.size() + 1; // account for null terminator
}
}
size = offset;
}
+
// Mergeable cstring literals are found under the __TEXT,__cstring section. In
// contrast to ELF, which puts strings that need different alignments into
// different sections, clang's Mach-O backend puts them all in one section.
// Strings that need to be aligned have the .p2align directive emitted before
-// them, which simply translates into zero padding in the object file.
+// them, which simply translates into zero padding in the object file. In other
+// words, we have to infer the desired alignment of these cstrings from their
+// addresses.
//
-// I *think* ld64 extracts the desired per-string alignment from this data by
-// preserving each string's offset from the last section-aligned address. I'm
-// not entirely certain since it doesn't seem consistent about doing this, and
-// in fact doesn't seem to be correct in general: we can in fact can induce ld64
-// to produce a crashing binary just by linking in an additional object file
-// that only contains a duplicate cstring at a different alignment. See PR50563
-// for details.
+// We differ slightly from ld64 in how we've chosen to align these cstrings.
+// Both LLD and ld64 preserve the number of trailing zeros in each cstring's
+// address in the input object files. When deduplicating identical cstrings,
+// both linkers pick the cstring whose address has more trailing zeros, and
+// preserve the alignment of that address in the final binary. However, ld64
+// goes a step further and also preserves the offset of the cstring from the
+// last section-aligned address. I.e. if a cstring is at offset 18 in the
+// input, with a section alignment of 16, then both LLD and ld64 will ensure the
+// final address is 2-byte aligned (since 18 == 16 + 2). But ld64 will also
+// ensure that the final address is of the form 16 * k + 2 for some k.
//
-// On x86_64, the cstrings we've seen so far that require special alignment are
-// all accessed by SIMD operations -- x86_64 requires SIMD accesses to be
-// 16-byte-aligned. arm64 also seems to require 16-byte-alignment in some cases
-// (PR50791), but I haven't tracked down the root cause. So for now, I'm just
-// aligning all strings to 16 bytes. This is indeed wasteful, but
-// implementation-wise it's simpler than preserving per-string
-// alignment+offsets. It also avoids the aforementioned crash after
-// deduplication of differently-aligned strings. Finally, the overhead is not
-// huge: using 16-byte alignment (vs no alignment) is only a 0.5% size overhead
-// when linking chromium_framework on x86_64.
-DeduplicatedCStringSection::DeduplicatedCStringSection()
- : builder(StringTableBuilder::RAW, /*Alignment=*/16) {}
-
+// Note that ld64's heuristic means that a dedup'ed cstring's final address is
+// dependent on the order of the input object files. E.g. if in addition to the
+// cstring at offset 18 above, we have a duplicate one in another file with a
+// `.cstring` section alignment of 2 and an offset of zero, then ld64 will pick
+// the cstring from the object file earlier on the command line (since both have
+// the same number of trailing zeros in their address). So the final cstring may
+// either be at some address `16 * k + 2` or at some address `2 * k`.
+//
+// I've opted not to follow this behavior primarily for implementation
+// simplicity, and secondarily to save a few more bytes. It's not clear to me
+// that preserving the section alignment + offset is ever necessary, and there
+// are many cases that are clearly redundant. In particular, if an x86_64 object
+// file contains some strings that are accessed via SIMD instructions, then the
+// .cstring section in the object file will be 16-byte-aligned (since SIMD
+// requires its operand addresses to be 16-byte aligned). However, there will
+// typically also be other cstrings in the same file that aren't used via SIMD
+// and don't need this alignment. They will be emitted at some arbitrary address
+// `A`, but ld64 will treat them as being 16-byte aligned with an offset of `16
+// % A`.
void DeduplicatedCStringSection::finalizeContents() {
- // Add all string pieces to the string table builder to create section
- // contents.
- for (const CStringInputSection *isec : inputs)
- for (size_t i = 0, e = isec->pieces.size(); i != e; ++i)
- if (isec->pieces[i].live)
- builder.add(isec->getCachedHashStringRef(i));
-
- // Fix the string table content. After this, the contents will never change.
- builder.finalizeInOrder();
-
- // finalize() fixed tail-optimized strings, so we can now get
- // offsets of strings. Get an offset for each string and save it
- // to a corresponding SectionPiece for easy access.
+ // Find the largest alignment required for each string.
+ for (const CStringInputSection *isec : inputs) {
+ for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+ if (!piece.live)
+ continue;
+ auto s = isec->getCachedHashStringRef(i);
+ assert(isec->align != 0);
+ uint8_t trailingZeros = countTrailingZeros(isec->align | piece.inSecOff);
+ auto it = stringOffsetMap.insert(
+ std::make_pair(s, StringOffset(trailingZeros)));
+ if (!it.second && it.first->second.trailingZeros < trailingZeros)
+ it.first->second.trailingZeros = trailingZeros;
+ }
+ }
+
+ // Assign an offset for each string and save it to the corresponding
+ // StringPieces for easy access.
for (CStringInputSection *isec : inputs) {
- for (size_t i = 0, e = isec->pieces.size(); i != e; ++i) {
- if (!isec->pieces[i].live)
+ for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+ if (!piece.live)
continue;
- isec->pieces[i].outSecOff =
- builder.getOffset(isec->getCachedHashStringRef(i));
- isec->isFinal = true;
+ auto s = isec->getCachedHashStringRef(i);
+ auto it = stringOffsetMap.find(s);
+ assert(it != stringOffsetMap.end());
+ StringOffset &offsetInfo = it->second;
+ if (offsetInfo.outSecOff == UINT64_MAX) {
+ offsetInfo.outSecOff = alignTo(size, 1ULL << offsetInfo.trailingZeros);
+ size =
+ offsetInfo.outSecOff + s.size() + 1; // account for null terminator
+ }
+ piece.outSecOff = offsetInfo.outSecOff;
}
+ isec->isFinal = true;
+ }
+}
+
+void DeduplicatedCStringSection::writeTo(uint8_t *buf) const {
+ for (const auto &p : stringOffsetMap) {
+ StringRef data = p.first.val();
+ uint64_t off = p.second.outSecOff;
+ if (!data.empty())
+ memcpy(buf + off, data.data(), data.size());
}
}
+DeduplicatedCStringSection::StringOffset
+DeduplicatedCStringSection::getStringOffset(StringRef str) const {
+ // StringPiece uses 31 bits to store the hashes, so we replicate that
+ uint32_t hash = xxHash64(str) & 0x7fffffff;
+ auto offset = stringOffsetMap.find(CachedHashStringRef(str, hash));
+ assert(offset != stringOffsetMap.end() &&
+ "Looked-up strings should always exist in section");
+ return offset->second;
+}
+
// This section is actually emitted as __TEXT,__const by ld64, but clang may
// emit input sections of that name, and LLD doesn't currently support mixing
// synthetic and concat-type OutputSections. To work around this, I've given
memcpy(buf + p.second * 4, &p.first, 4);
}
+ObjCImageInfoSection::ObjCImageInfoSection()
+ : SyntheticSection(segment_names::data, section_names::objCImageInfo) {}
+
+ObjCImageInfoSection::ImageInfo
+ObjCImageInfoSection::parseImageInfo(const InputFile *file) {
+ ImageInfo info;
+ ArrayRef<uint8_t> data = file->objCImageInfo;
+ // The image info struct has the following layout:
+ // struct {
+ // uint32_t version;
+ // uint32_t flags;
+ // };
+ if (data.size() < 8) {
+ warn(toString(file) + ": invalid __objc_imageinfo size");
+ return info;
+ }
+
+ auto *buf = reinterpret_cast<const uint32_t *>(data.data());
+ if (read32le(buf) != 0) {
+ warn(toString(file) + ": invalid __objc_imageinfo version");
+ return info;
+ }
+
+ uint32_t flags = read32le(buf + 1);
+ info.swiftVersion = (flags >> 8) & 0xff;
+ info.hasCategoryClassProperties = flags & 0x40;
+ return info;
+}
+
+static std::string swiftVersionString(uint8_t version) {
+ switch (version) {
+ case 1:
+ return "1.0";
+ case 2:
+ return "1.1";
+ case 3:
+ return "2.0";
+ case 4:
+ return "3.0";
+ case 5:
+ return "4.0";
+ default:
+ return ("0x" + Twine::utohexstr(version)).str();
+ }
+}
+
+// Validate each object file's __objc_imageinfo and use them to generate the
+// image info for the output binary. Only two pieces of info are relevant:
+// 1. The Swift version (should be identical across inputs)
+// 2. `bool hasCategoryClassProperties` (true only if true for all inputs)
+void ObjCImageInfoSection::finalizeContents() {
+ assert(files.size() != 0); // should have already been checked via isNeeded()
+
+ info.hasCategoryClassProperties = true;
+ const InputFile *firstFile;
+ for (auto file : files) {
+ ImageInfo inputInfo = parseImageInfo(file);
+ info.hasCategoryClassProperties &= inputInfo.hasCategoryClassProperties;
+
+ // swiftVersion 0 means no Swift is present, so no version checking required
+ if (inputInfo.swiftVersion == 0)
+ continue;
+
+ if (info.swiftVersion != 0 && info.swiftVersion != inputInfo.swiftVersion) {
+ error("Swift version mismatch: " + toString(firstFile) + " has version " +
+ swiftVersionString(info.swiftVersion) + " but " + toString(file) +
+ " has version " + swiftVersionString(inputInfo.swiftVersion));
+ } else {
+ info.swiftVersion = inputInfo.swiftVersion;
+ firstFile = file;
+ }
+ }
+}
+
+void ObjCImageInfoSection::writeTo(uint8_t *buf) const {
+ uint32_t flags = info.hasCategoryClassProperties ? 0x40 : 0x0;
+ flags |= info.swiftVersion << 8;
+ write32le(buf + 4, flags);
+}
+
+InitOffsetsSection::InitOffsetsSection()
+ : SyntheticSection(segment_names::text, section_names::initOffsets) {
+ flags = S_INIT_FUNC_OFFSETS;
+ align = 4; // This section contains 32-bit integers.
+}
+
+uint64_t InitOffsetsSection::getSize() const {
+ size_t count = 0;
+ for (const ConcatInputSection *isec : sections)
+ count += isec->relocs.size();
+ return count * sizeof(uint32_t);
+}
+
+void InitOffsetsSection::writeTo(uint8_t *buf) const {
+ // FIXME: Add function specified by -init when that argument is implemented.
+ for (ConcatInputSection *isec : sections) {
+ for (const Reloc &rel : isec->relocs) {
+ const Symbol *referent = rel.referent.dyn_cast<Symbol *>();
+ assert(referent && "section relocation should have been rejected");
+ uint64_t offset = referent->getVA() - in.header->addr;
+ // FIXME: Can we handle this gracefully?
+ if (offset > UINT32_MAX)
+ fatal(isec->getLocation(rel.offset) + ": offset to initializer " +
+ referent->getName() + " (" + utohexstr(offset) +
+ ") does not fit in 32 bits");
+
+ // Entries need to be added in the order they appear in the section, but
+ // relocations aren't guaranteed to be sorted.
+ size_t index = rel.offset >> target->p2WordSize;
+ write32le(&buf[index * sizeof(uint32_t)], offset);
+ }
+ buf += isec->relocs.size() * sizeof(uint32_t);
+ }
+}
+
+// The inputs are __mod_init_func sections, which contain pointers to
+// initializer functions, therefore all relocations should be of the UNSIGNED
+// type. InitOffsetsSection stores offsets, so if the initializer's address is
+// not known at link time, stub-indirection has to be used.
+void InitOffsetsSection::setUp() {
+ for (const ConcatInputSection *isec : sections) {
+ for (const Reloc &rel : isec->relocs) {
+ RelocAttrs attrs = target->getRelocAttrs(rel.type);
+ if (!attrs.hasAttr(RelocAttrBits::UNSIGNED))
+ error(isec->getLocation(rel.offset) +
+ ": unsupported relocation type: " + attrs.name);
+ if (rel.addend != 0)
+ error(isec->getLocation(rel.offset) +
+ ": relocation addend is not representable in __init_offsets");
+ if (rel.referent.is<InputSection *>())
+ error(isec->getLocation(rel.offset) +
+ ": unexpected section relocation");
+
+ Symbol *sym = rel.referent.dyn_cast<Symbol *>();
+ if (auto *undefined = dyn_cast<Undefined>(sym))
+ treatUndefinedSymbol(*undefined, isec, rel.offset);
+ if (needsBinding(sym))
+ in.stubs->addEntry(sym);
+ }
+ }
+}
+
void macho::createSyntheticSymbols() {
auto addHeaderSymbol = [](const char *name) {
symtab->addSynthetic(name, in.header->isec, /*value=*/0,
- /*privateExtern=*/true, /*includeInSymtab=*/false,
+ /*isPrivateExtern=*/true, /*includeInSymtab=*/false,
/*referencedDynamically=*/false);
};
// Otherwise, it's an absolute symbol.
if (config->isPic)
symtab->addSynthetic("__mh_execute_header", in.header->isec, /*value=*/0,
- /*privateExtern=*/false, /*includeInSymtab=*/true,
+ /*isPrivateExtern=*/false, /*includeInSymtab=*/true,
/*referencedDynamically=*/true);
else
symtab->addSynthetic("__mh_execute_header", /*isec=*/nullptr, /*value=*/0,
- /*privateExtern=*/false, /*includeInSymtab=*/true,
+ /*isPrivateExtern=*/false, /*includeInSymtab=*/true,
/*referencedDynamically=*/true);
break;
addHeaderSymbol("___dso_handle");
}
+ChainedFixupsSection::ChainedFixupsSection()
+ : LinkEditSection(segment_names::linkEdit, section_names::chainFixups) {}
+
+bool ChainedFixupsSection::isNeeded() const {
+ assert(config->emitChainedFixups);
+ // dyld always expects LC_DYLD_CHAINED_FIXUPS to point to a valid
+ // dyld_chained_fixups_header, so we create this section even if there aren't
+ // any fixups.
+ return true;
+}
+
+static bool needsWeakBind(const Symbol &sym) {
+ if (auto *dysym = dyn_cast<DylibSymbol>(&sym))
+ return dysym->isWeakDef();
+ if (auto *defined = dyn_cast<Defined>(&sym))
+ return defined->isExternalWeakDef();
+ return false;
+}
+
+void ChainedFixupsSection::addBinding(const Symbol *sym,
+ const InputSection *isec, uint64_t offset,
+ int64_t addend) {
+ locations.emplace_back(isec, offset);
+ int64_t outlineAddend = (addend < 0 || addend > 0xFF) ? addend : 0;
+ auto [it, inserted] = bindings.insert(
+ {{sym, outlineAddend}, static_cast<uint32_t>(bindings.size())});
+
+ if (inserted) {
+ symtabSize += sym->getName().size() + 1;
+ hasWeakBind = hasWeakBind || needsWeakBind(*sym);
+ if (!isInt<23>(outlineAddend))
+ needsLargeAddend = true;
+ else if (outlineAddend != 0)
+ needsAddend = true;
+ }
+}
+
+std::pair<uint32_t, uint8_t>
+ChainedFixupsSection::getBinding(const Symbol *sym, int64_t addend) const {
+ int64_t outlineAddend = (addend < 0 || addend > 0xFF) ? addend : 0;
+ auto it = bindings.find({sym, outlineAddend});
+ assert(it != bindings.end() && "binding not found in the imports table");
+ if (outlineAddend == 0)
+ return {it->second, addend};
+ return {it->second, 0};
+}
+
+static size_t writeImport(uint8_t *buf, int format, uint32_t libOrdinal,
+ bool weakRef, uint32_t nameOffset, int64_t addend) {
+ switch (format) {
+ case DYLD_CHAINED_IMPORT: {
+ auto *import = reinterpret_cast<dyld_chained_import *>(buf);
+ import->lib_ordinal = libOrdinal;
+ import->weak_import = weakRef;
+ import->name_offset = nameOffset;
+ return sizeof(dyld_chained_import);
+ }
+ case DYLD_CHAINED_IMPORT_ADDEND: {
+ auto *import = reinterpret_cast<dyld_chained_import_addend *>(buf);
+ import->lib_ordinal = libOrdinal;
+ import->weak_import = weakRef;
+ import->name_offset = nameOffset;
+ import->addend = addend;
+ return sizeof(dyld_chained_import_addend);
+ }
+ case DYLD_CHAINED_IMPORT_ADDEND64: {
+ auto *import = reinterpret_cast<dyld_chained_import_addend64 *>(buf);
+ import->lib_ordinal = libOrdinal;
+ import->weak_import = weakRef;
+ import->name_offset = nameOffset;
+ import->addend = addend;
+ return sizeof(dyld_chained_import_addend64);
+ }
+ default:
+ llvm_unreachable("Unknown import format");
+ }
+}
+
+size_t ChainedFixupsSection::SegmentInfo::getSize() const {
+ assert(pageStarts.size() > 0 && "SegmentInfo for segment with no fixups?");
+ return alignTo<8>(sizeof(dyld_chained_starts_in_segment) +
+ pageStarts.back().first * sizeof(uint16_t));
+}
+
+size_t ChainedFixupsSection::SegmentInfo::writeTo(uint8_t *buf) const {
+ auto *segInfo = reinterpret_cast<dyld_chained_starts_in_segment *>(buf);
+ segInfo->size = getSize();
+ segInfo->page_size = target->getPageSize();
+ // FIXME: Use DYLD_CHAINED_PTR_64_OFFSET on newer OS versions.
+ segInfo->pointer_format = DYLD_CHAINED_PTR_64;
+ segInfo->segment_offset = oseg->addr - in.header->addr;
+ segInfo->max_valid_pointer = 0; // not used on 64-bit
+ segInfo->page_count = pageStarts.back().first + 1;
+
+ uint16_t *starts = segInfo->page_start;
+ for (size_t i = 0; i < segInfo->page_count; ++i)
+ starts[i] = DYLD_CHAINED_PTR_START_NONE;
+
+ for (auto [pageIdx, startAddr] : pageStarts)
+ starts[pageIdx] = startAddr;
+ return segInfo->size;
+}
+
+static size_t importEntrySize(int format) {
+ switch (format) {
+ case DYLD_CHAINED_IMPORT:
+ return sizeof(dyld_chained_import);
+ case DYLD_CHAINED_IMPORT_ADDEND:
+ return sizeof(dyld_chained_import_addend);
+ case DYLD_CHAINED_IMPORT_ADDEND64:
+ return sizeof(dyld_chained_import_addend64);
+ default:
+ llvm_unreachable("Unknown import format");
+ }
+}
+
+// This is step 3 of the algorithm described in the class comment of
+// ChainedFixupsSection.
+//
+// LC_DYLD_CHAINED_FIXUPS data consists of (in this order):
+// * A dyld_chained_fixups_header
+// * A dyld_chained_starts_in_image
+// * One dyld_chained_starts_in_segment per segment
+// * List of all imports (dyld_chained_import, dyld_chained_import_addend, or
+// dyld_chained_import_addend64)
+// * Names of imported symbols
+void ChainedFixupsSection::writeTo(uint8_t *buf) const {
+ auto *header = reinterpret_cast<dyld_chained_fixups_header *>(buf);
+ header->fixups_version = 0;
+ header->imports_count = bindings.size();
+ header->imports_format = importFormat;
+ header->symbols_format = 0;
+
+ buf += alignTo<8>(sizeof(*header));
+
+ auto curOffset = [&buf, &header]() -> uint32_t {
+ return buf - reinterpret_cast<uint8_t *>(header);
+ };
+
+ header->starts_offset = curOffset();
+
+ auto *imageInfo = reinterpret_cast<dyld_chained_starts_in_image *>(buf);
+ imageInfo->seg_count = outputSegments.size();
+ uint32_t *segStarts = imageInfo->seg_info_offset;
+
+ // dyld_chained_starts_in_image ends in a flexible array member containing an
+ // uint32_t for each segment. Leave room for it, and fill it via segStarts.
+ buf += alignTo<8>(offsetof(dyld_chained_starts_in_image, seg_info_offset) +
+ outputSegments.size() * sizeof(uint32_t));
+
+ // Initialize all offsets to 0, which indicates that the segment does not have
+ // fixups. Those that do have them will be filled in below.
+ for (size_t i = 0; i < outputSegments.size(); ++i)
+ segStarts[i] = 0;
+
+ for (const SegmentInfo &seg : fixupSegments) {
+ segStarts[seg.oseg->index] = curOffset() - header->starts_offset;
+ buf += seg.writeTo(buf);
+ }
+
+ // Write imports table.
+ header->imports_offset = curOffset();
+ uint64_t nameOffset = 0;
+ for (auto [import, idx] : bindings) {
+ const Symbol &sym = *import.first;
+ int16_t libOrdinal = needsWeakBind(sym)
+ ? (int64_t)BIND_SPECIAL_DYLIB_WEAK_LOOKUP
+ : ordinalForSymbol(sym);
+ buf += writeImport(buf, importFormat, libOrdinal, sym.isWeakRef(),
+ nameOffset, import.second);
+ nameOffset += sym.getName().size() + 1;
+ }
+
+ // Write imported symbol names.
+ header->symbols_offset = curOffset();
+ for (auto [import, idx] : bindings) {
+ StringRef name = import.first->getName();
+ memcpy(buf, name.data(), name.size());
+ buf += name.size() + 1; // account for null terminator
+ }
+
+ assert(curOffset() == getRawSize());
+}
+
+// This is step 2 of the algorithm described in the class comment of
+// ChainedFixupsSection.
+void ChainedFixupsSection::finalizeContents() {
+ assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
+ assert(config->emitChainedFixups);
+
+ if (!isUInt<32>(symtabSize))
+ error("cannot encode chained fixups: imported symbols table size " +
+ Twine(symtabSize) + " exceeds 4 GiB");
+
+ if (needsLargeAddend || !isUInt<23>(symtabSize))
+ importFormat = DYLD_CHAINED_IMPORT_ADDEND64;
+ else if (needsAddend)
+ importFormat = DYLD_CHAINED_IMPORT_ADDEND;
+ else
+ importFormat = DYLD_CHAINED_IMPORT;
+
+ for (Location &loc : locations)
+ loc.offset =
+ loc.isec->parent->getSegmentOffset() + loc.isec->getOffset(loc.offset);
+
+ llvm::sort(locations, [](const Location &a, const Location &b) {
+ const OutputSegment *segA = a.isec->parent->parent;
+ const OutputSegment *segB = b.isec->parent->parent;
+ if (segA == segB)
+ return a.offset < b.offset;
+ return segA->addr < segB->addr;
+ });
+
+ auto sameSegment = [](const Location &a, const Location &b) {
+ return a.isec->parent->parent == b.isec->parent->parent;
+ };
+
+ const uint64_t pageSize = target->getPageSize();
+ for (size_t i = 0, count = locations.size(); i < count;) {
+ const Location &firstLoc = locations[i];
+ fixupSegments.emplace_back(firstLoc.isec->parent->parent);
+ while (i < count && sameSegment(locations[i], firstLoc)) {
+ uint32_t pageIdx = locations[i].offset / pageSize;
+ fixupSegments.back().pageStarts.emplace_back(
+ pageIdx, locations[i].offset % pageSize);
+ ++i;
+ while (i < count && sameSegment(locations[i], firstLoc) &&
+ locations[i].offset / pageSize == pageIdx)
+ ++i;
+ }
+ }
+
+ // Compute expected encoded size.
+ size = alignTo<8>(sizeof(dyld_chained_fixups_header));
+ size += alignTo<8>(offsetof(dyld_chained_starts_in_image, seg_info_offset) +
+ outputSegments.size() * sizeof(uint32_t));
+ for (const SegmentInfo &seg : fixupSegments)
+ size += seg.getSize();
+ size += importEntrySize(importFormat) * bindings.size();
+ size += symtabSize;
+}
+
template SymtabSection *macho::makeSymtabSection<LP64>(StringTableSection &);
template SymtabSection *macho::makeSymtabSection<ILP32>(StringTableSection &);
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SetVector.h"
-#include "llvm/MC/StringTableBuilder.h"
+#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
class DWARFUnit;
} // namespace llvm
-namespace lld {
-namespace macho {
+namespace lld::macho {
class Defined;
class DylibSymbol;
align = target->wordSize;
}
+ // Implementations of this method can assume that the regular (non-__LINKEDIT)
+ // sections already have their addresses assigned.
virtual void finalizeContents() {}
// Sections in __LINKEDIT are special: their offsets are recorded in the
// load commands like LC_DYLD_INFO_ONLY and LC_SYMTAB, instead of in section
// headers.
- bool isHidden() const override final { return true; }
+ bool isHidden() const final { return true; }
virtual uint64_t getRawSize() const = 0;
//
// NOTE: This assumes that the extra bytes required for alignment can be
// zero-valued bytes.
- uint64_t getSize() const override final {
- return llvm::alignTo(getRawSize(), align);
- }
+ uint64_t getSize() const final { return llvm::alignTo(getRawSize(), align); }
};
// The header of the Mach-O file, which must have a file offset of zero.
public:
PageZeroSection();
bool isHidden() const override { return true; }
+ bool isNeeded() const override { return target->pageZeroSize != 0; }
uint64_t getSize() const override { return target->pageZeroSize; }
uint64_t getFileSize() const override { return 0; }
void writeTo(uint8_t *buf) const override {}
bool isNeeded() const override { return !bindingsMap.empty(); }
void writeTo(uint8_t *buf) const override;
- void addEntry(const DylibSymbol *dysym, const InputSection *isec,
- uint64_t offset, int64_t addend = 0) {
+ void addEntry(const Symbol *dysym, const InputSection *isec, uint64_t offset,
+ int64_t addend = 0) {
bindingsMap[dysym].emplace_back(addend, Location(isec, offset));
}
private:
- BindingsMap<const DylibSymbol *> bindingsMap;
+ BindingsMap<const Symbol *> bindingsMap;
SmallVector<char, 128> contents;
};
// order that the weak bindings may overwrite the non-lazy bindings if an
// appropriate symbol is found at runtime. However, the bound addresses will
// still be written (non-lazily) into the LazyPointerSection.
+//
+// Symbols are always bound eagerly when chained fixups are used. In that case,
+// StubsSection contains indirect jumps to addresses stored in the GotSection.
+// The GOT directly contains the fixup entries, which will be replaced by the
+// address of the target symbols on load. LazyPointerSection and
+// StubHelperSection are not used.
class StubsSection final : public SyntheticSection {
public:
void finalize() override;
void writeTo(uint8_t *buf) const override;
const llvm::SetVector<Symbol *> &getEntries() const { return entries; }
- // Returns whether the symbol was added. Note that every stubs entry will
- // have a corresponding entry in the LazyPointerSection.
- bool addEntry(Symbol *);
+ // Creates a stub for the symbol and the corresponding entry in the
+ // LazyPointerSection.
+ void addEntry(Symbol *);
uint64_t getVA(uint32_t stubsIndex) const {
assert(isFinal || target->usesThunks());
// ConcatOutputSection::finalize() can seek the address of a
bool isNeeded() const override;
void writeTo(uint8_t *buf) const override;
- void setup();
+ void setUp();
DylibSymbol *stubBinder = nullptr;
Defined *dyldPrivate = nullptr;
};
+// Objective-C stubs are hoisted objc_msgSend calls per selector called in the
+// program. Apple Clang produces undefined symbols to each stub, such as
+// '_objc_msgSend$foo', which are then synthesized by the linker. The stubs
+// load the particular selector 'foo' from __objc_selrefs, setting it to the
+// first argument of the objc_msgSend call, and then jumps to objc_msgSend. The
+// actual stub contents are mirrored from ld64.
+class ObjCStubsSection final : public SyntheticSection {
+public:
+ ObjCStubsSection();
+ void addEntry(Symbol *sym);
+ uint64_t getSize() const override;
+ bool isNeeded() const override { return !symbols.empty(); }
+ void finalize() override { isec->isFinal = true; }
+ void writeTo(uint8_t *buf) const override;
+ void setUp();
+
+ static constexpr llvm::StringLiteral symbolPrefix = "_objc_msgSend$";
+
+private:
+ std::vector<Defined *> symbols;
+ std::vector<uint32_t> offsets;
+ int objcMsgSendGotIndex = 0;
+};
+
// Note that this section may also be targeted by non-lazy bindings. In
// particular, this happens when branch relocations target weak symbols.
class LazyPointerSection final : public SyntheticSection {
uint64_t getSize() const override;
bool isNeeded() const override;
void writeTo(uint8_t *buf) const override;
+ uint64_t getVA(uint32_t index) const {
+ return addr + (index << target->p2WordSize);
+ }
};
class LazyBindingSection final : public LinkEditSection {
void writeTo(uint8_t *buf) const override;
// Note that every entry here will by referenced by a corresponding entry in
// the StubHelperSection.
- void addEntry(DylibSymbol *dysym);
- const llvm::SetVector<DylibSymbol *> &getEntries() const { return entries; }
+ void addEntry(Symbol *dysym);
+ const llvm::SetVector<Symbol *> &getEntries() const { return entries; }
private:
- uint32_t encode(const DylibSymbol &);
+ uint32_t encode(const Symbol &);
- llvm::SetVector<DylibSymbol *> entries;
+ llvm::SetVector<Symbol *> entries;
SmallVector<char, 128> contents;
llvm::raw_svector_ostream os{contents};
};
ExportSection();
void finalizeContents() override;
uint64_t getRawSize() const override { return size; }
+ bool isNeeded() const override { return size; }
void writeTo(uint8_t *buf) const override;
bool hasWeakSymbol = false;
size_t size = 0;
};
-// Stores 'data in code' entries that describe the locations of
-// data regions inside code sections.
+// Stores 'data in code' entries that describe the locations of data regions
+// inside code sections. This is used by llvm-objdump to distinguish jump tables
+// and stop them from being disassembled as instructions.
class DataInCodeSection final : public LinkEditSection {
public:
DataInCodeSection();
uint32_t getNumUndefinedSymbols() const { return undefinedSymbols.size(); }
private:
- void emitBeginSourceStab(llvm::DWARFUnit *compileUnit);
+ void emitBeginSourceStab(StringRef);
void emitEndSourceStab();
void emitObjectFileStab(ObjFile *);
void emitEndFunStab(Defined *);
// The code signature comes at the very end of the linked output file.
class CodeSignatureSection final : public LinkEditSection {
public:
+ // NOTE: These values are duplicated in llvm-objcopy's MachO/Object.h file
+ // and any changes here, should be repeated there.
static constexpr uint8_t blockSizeShift = 12;
static constexpr size_t blockSize = (1 << blockSizeShift); // 4 KiB
static constexpr size_t hashSize = 256 / 8;
class CStringSection : public SyntheticSection {
public:
- CStringSection();
+ CStringSection(const char *name);
void addInput(CStringInputSection *);
uint64_t getSize() const override { return size; }
virtual void finalizeContents();
class DeduplicatedCStringSection final : public CStringSection {
public:
- DeduplicatedCStringSection();
- uint64_t getSize() const override { return builder.getSize(); }
+ DeduplicatedCStringSection(const char *name) : CStringSection(name){};
+ uint64_t getSize() const override { return size; }
void finalizeContents() override;
- void writeTo(uint8_t *buf) const override { builder.write(buf); }
+ void writeTo(uint8_t *buf) const override;
+
+ struct StringOffset {
+ uint8_t trailingZeros;
+ uint64_t outSecOff = UINT64_MAX;
+
+ explicit StringOffset(uint8_t zeros) : trailingZeros(zeros) {}
+ };
+
+ StringOffset getStringOffset(StringRef str) const;
private:
- llvm::StringTableBuilder builder;
+ llvm::DenseMap<llvm::CachedHashStringRef, StringOffset> stringOffsetMap;
+ size_t size = 0;
};
/*
using UInt128 = std::pair<uint64_t, uint64_t>;
// I don't think the standard guarantees the size of a pair, so let's make
// sure it's exact -- that way we can construct it via `mmap`.
- static_assert(sizeof(UInt128) == 16, "");
+ static_assert(sizeof(UInt128) == 16);
WordLiteralSection();
void addInput(WordLiteralInputSection *);
!literal8Map.empty();
}
- uint64_t getLiteral16Offset(const uint8_t *buf) const {
+ uint64_t getLiteral16Offset(uintptr_t buf) const {
return literal16Map.at(*reinterpret_cast<const UInt128 *>(buf)) * 16;
}
- uint64_t getLiteral8Offset(const uint8_t *buf) const {
+ uint64_t getLiteral8Offset(uintptr_t buf) const {
return literal16Map.size() * 16 +
literal8Map.at(*reinterpret_cast<const uint64_t *>(buf)) * 8;
}
- uint64_t getLiteral4Offset(const uint8_t *buf) const {
+ uint64_t getLiteral4Offset(uintptr_t buf) const {
return literal16Map.size() * 16 + literal8Map.size() * 8 +
literal4Map.at(*reinterpret_cast<const uint32_t *>(buf)) * 4;
}
std::unordered_map<uint32_t, uint64_t> literal4Map;
};
+class ObjCImageInfoSection final : public SyntheticSection {
+public:
+ ObjCImageInfoSection();
+ bool isNeeded() const override { return !files.empty(); }
+ uint64_t getSize() const override { return 8; }
+ void addFile(const InputFile *file) {
+ assert(!file->objCImageInfo.empty());
+ files.push_back(file);
+ }
+ void finalizeContents();
+ void writeTo(uint8_t *buf) const override;
+
+private:
+ struct ImageInfo {
+ uint8_t swiftVersion = 0;
+ bool hasCategoryClassProperties = false;
+ } info;
+ static ImageInfo parseImageInfo(const InputFile *);
+ std::vector<const InputFile *> files; // files with image info
+};
+
+// This section stores 32-bit __TEXT segment offsets of initializer functions.
+//
+// The compiler stores pointers to initializers in __mod_init_func. These need
+// to be fixed up at load time, which takes time and dirties memory. By
+// synthesizing InitOffsetsSection from them, this data can live in the
+// read-only __TEXT segment instead. This section is used by default when
+// chained fixups are enabled.
+//
+// There is no similar counterpart to __mod_term_func, as that section is
+// deprecated, and static destructors are instead handled by registering them
+// via __cxa_atexit from an autogenerated initializer function (see D121736).
+class InitOffsetsSection final : public SyntheticSection {
+public:
+ InitOffsetsSection();
+ bool isNeeded() const override { return !sections.empty(); }
+ uint64_t getSize() const override;
+ void writeTo(uint8_t *buf) const override;
+ void setUp();
+
+ void addInput(ConcatInputSection *isec) { sections.push_back(isec); }
+ const std::vector<ConcatInputSection *> &inputs() const { return sections; }
+
+private:
+ std::vector<ConcatInputSection *> sections;
+};
+
+// Chained fixups are a replacement for classic dyld opcodes. In this format,
+// most of the metadata necessary for binding symbols and rebasing addresses is
+// stored directly in the memory location that will have the fixup applied.
+//
+// The fixups form singly linked lists; each one covering a single page in
+// memory. The __LINKEDIT,__chainfixups section stores the page offset of the
+// first fixup of each page; the rest can be found by walking the chain using
+// the offset that is embedded in each entry.
+//
+// This setup allows pages to be relocated lazily at page-in time and without
+// being dirtied. The kernel can discard and load them again as needed. This
+// technique, called page-in linking, was introduced in macOS 13.
+//
+// The benefits of this format are:
+// - smaller __LINKEDIT segment, as most of the fixup information is stored in
+// the data segment
+// - faster startup, since not all relocations need to be done upfront
+// - slightly lower memory usage, as fewer pages are dirtied
+//
+// Userspace x86_64 and arm64 binaries have two types of fixup entries:
+// - Rebase entries contain an absolute address, to which the object's load
+// address will be added to get the final value. This is used for loading
+// the address of a symbol defined in the same binary.
+// - Binding entries are mostly used for symbols imported from other dylibs,
+// but for weakly bound and interposable symbols as well. They are looked up
+// by a (symbol name, library) pair stored in __chainfixups. This import
+// entry also encodes whether the import is weak (i.e. if the symbol is
+// missing, it should be set to null instead of producing a load error).
+// The fixup encodes an ordinal associated with the import, and an optional
+// addend.
+//
+// The entries are tightly packed 64-bit bitfields. One of the bits specifies
+// which kind of fixup to interpret them as.
+//
+// LLD generates the fixup data in 5 stages:
+// 1. While scanning relocations, we make a note of each location that needs
+// a fixup by calling addRebase() or addBinding(). During this, we assign
+// a unique ordinal for each (symbol name, library, addend) import tuple.
+// 2. After addresses have been assigned to all sections, and thus the memory
+// layout of the linked image is final; finalizeContents() is called. Here,
+// the page offsets of the chain start entries are calculated.
+// 3. ChainedFixupsSection::writeTo() writes the page start offsets and the
+// imports table to the output file.
+// 4. Each section's fixup entries are encoded and written to disk in
+// ConcatInputSection::writeTo(), but without writing the offsets that form
+// the chain.
+// 5. Finally, each page's (which might correspond to multiple sections)
+// fixups are linked together in Writer::buildFixupChains().
+class ChainedFixupsSection final : public LinkEditSection {
+public:
+ ChainedFixupsSection();
+ void finalizeContents() override;
+ uint64_t getRawSize() const override { return size; }
+ bool isNeeded() const override;
+ void writeTo(uint8_t *buf) const override;
+
+ void addRebase(const InputSection *isec, uint64_t offset) {
+ locations.emplace_back(isec, offset);
+ }
+ void addBinding(const Symbol *dysym, const InputSection *isec,
+ uint64_t offset, int64_t addend = 0);
+
+ void setHasNonWeakDefinition() { hasNonWeakDef = true; }
+
+ // Returns an (ordinal, inline addend) tuple used by dyld_chained_ptr_64_bind.
+ std::pair<uint32_t, uint8_t> getBinding(const Symbol *sym,
+ int64_t addend) const;
+
+ const std::vector<Location> &getLocations() const { return locations; }
+
+ bool hasWeakBinding() const { return hasWeakBind; }
+ bool hasNonWeakDefinition() const { return hasNonWeakDef; }
+
+private:
+ // Location::offset initially stores the offset within an InputSection, but
+ // contains output segment offsets after finalizeContents().
+ std::vector<Location> locations;
+ // (target symbol, addend) => import ordinal
+ llvm::MapVector<std::pair<const Symbol *, int64_t>, uint32_t> bindings;
+
+ struct SegmentInfo {
+ SegmentInfo(const OutputSegment *oseg) : oseg(oseg) {}
+
+ const OutputSegment *oseg;
+ // (page index, fixup starts offset)
+ llvm::SmallVector<std::pair<uint16_t, uint16_t>> pageStarts;
+
+ size_t getSize() const;
+ size_t writeTo(uint8_t *buf) const;
+ };
+ llvm::SmallVector<SegmentInfo, 4> fixupSegments;
+
+ size_t symtabSize = 0;
+ size_t size = 0;
+
+ bool needsAddend = false;
+ bool needsLargeAddend = false;
+ bool hasWeakBind = false;
+ bool hasNonWeakDef = false;
+ llvm::MachO::ChainedImportFormat importFormat;
+};
+
+void writeChainedRebase(uint8_t *buf, uint64_t targetVA);
+void writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend);
+
struct InStruct {
+ const uint8_t *bufferStart = nullptr;
MachHeaderSection *header = nullptr;
CStringSection *cStringSection = nullptr;
+ DeduplicatedCStringSection *objcMethnameSection = nullptr;
WordLiteralSection *wordLiteralSection = nullptr;
RebaseSection *rebase = nullptr;
BindingSection *binding = nullptr;
LazyPointerSection *lazyPointers = nullptr;
StubsSection *stubs = nullptr;
StubHelperSection *stubHelper = nullptr;
+ ObjCStubsSection *objcStubs = nullptr;
+ ConcatInputSection *objcSelrefs = nullptr;
UnwindInfoSection *unwindInfo = nullptr;
+ ObjCImageInfoSection *objCImageInfo = nullptr;
ConcatInputSection *imageLoaderCache = nullptr;
+ InitOffsetsSection *initOffsets = nullptr;
+ ChainedFixupsSection *chainedFixups = nullptr;
};
extern InStruct in;
void createSyntheticSymbols();
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/BinaryFormat/MachO.h"
+#include "llvm/Support/MathExtras.h"
#include "llvm/Support/MemoryBuffer.h"
#include <cstddef>
#include <cstdint>
-namespace lld {
-namespace macho {
+#include "mach-o/compact_unwind_encoding.h"
+
+namespace lld::macho {
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
class Symbol;
class Defined;
class DylibSymbol;
class InputSection;
+class ObjFile;
+
+static_assert(static_cast<uint32_t>(UNWIND_X86_64_MODE_MASK) ==
+ static_cast<uint32_t>(UNWIND_X86_MODE_MASK) &&
+ static_cast<uint32_t>(UNWIND_ARM64_MODE_MASK) ==
+ static_cast<uint32_t>(UNWIND_X86_64_MODE_MASK));
+
+// Since the mode masks have the same value on all targets, define
+// a common one for convenience.
+constexpr uint32_t UNWIND_MODE_MASK = UNWIND_X86_64_MODE_MASK;
class TargetInfo {
public:
pageZeroSize = LP::pageZeroSize;
headerSize = sizeof(typename LP::mach_header);
wordSize = LP::wordSize;
+ p2WordSize = llvm::CTLog2<LP::wordSize>();
}
virtual ~TargetInfo() = default;
// Write code for lazy binding. See the comments on StubsSection for more
// details.
- virtual void writeStub(uint8_t *buf, const Symbol &) const = 0;
+ virtual void writeStub(uint8_t *buf, const Symbol &,
+ uint64_t pointerVA) const = 0;
virtual void writeStubHelperHeader(uint8_t *buf) const = 0;
- virtual void writeStubHelperEntry(uint8_t *buf, const DylibSymbol &,
+ virtual void writeStubHelperEntry(uint8_t *buf, const Symbol &,
uint64_t entryAddr) const = 0;
+ virtual void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym,
+ uint64_t stubsAddr, uint64_t stubOffset,
+ uint64_t selrefsVA, uint64_t selectorIndex,
+ uint64_t gotAddr,
+ uint64_t msgSendIndex) const = 0;
+
// Symbols may be referenced via either the GOT or the stubs section,
// depending on the relocation type. prepareSymbolRelocation() will set up the
// GOT/stubs entries, and resolveSymbolVA() will return the addresses of those
// on a level of address indirection.
virtual void relaxGotLoad(uint8_t *loc, uint8_t type) const = 0;
- virtual const RelocAttrs &getRelocAttrs(uint8_t type) const = 0;
-
virtual uint64_t getPageSize() const = 0;
virtual void populateThunk(InputSection *thunk, Symbol *funcSym) {
llvm_unreachable("target does not use thunks");
}
+ const RelocAttrs &getRelocAttrs(uint8_t type) const {
+ assert(type < relocAttrs.size() && "invalid relocation type");
+ if (type >= relocAttrs.size())
+ return invalidRelocAttrs;
+ return relocAttrs[type];
+ }
+
bool hasAttr(uint8_t type, RelocAttrBits bit) const {
return getRelocAttrs(type).hasAttr(bit);
}
bool usesThunks() const { return thunkSize > 0; }
+ // For now, handleDtraceReloc only implements -no_dtrace_dof, and ensures
+ // that the linking would not fail even when there are user-provided dtrace
+ // symbols. However, unlike ld64, lld currently does not emit __dof sections.
+ virtual void handleDtraceReloc(const Symbol *sym, const Reloc &r,
+ uint8_t *loc) const {
+ llvm_unreachable("Unsupported architecture for dtrace symbols");
+ }
+
+ virtual void applyOptimizationHints(uint8_t *, const ObjFile &) const {};
+
uint32_t magic;
llvm::MachO::CPUType cpuType;
uint32_t cpuSubtype;
size_t stubSize;
size_t stubHelperHeaderSize;
size_t stubHelperEntrySize;
+ size_t objcStubsFastSize;
+ size_t objcStubsAlignment;
+ uint8_t p2WordSize;
size_t wordSize;
size_t thunkSize = 0;
- uint64_t branchRange = 0;
+ uint64_t forwardBranchRange = 0;
+ uint64_t backwardBranchRange = 0;
+
+ uint32_t modeDwarfEncoding;
+ uint8_t subtractorRelocType;
+ uint8_t unsignedRelocType;
+
+ llvm::ArrayRef<RelocAttrs> relocAttrs;
// We contrive this value as sufficiently far from any valid address that it
// will always be out-of-range for any architecture. UINT64_MAX is not a
extern TargetInfo *target;
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
//===----------------------------------------------------------------------===//
#include "UnwindInfoSection.h"
-#include "ConcatOutputSection.h"
-#include "Config.h"
#include "InputSection.h"
#include "OutputSection.h"
#include "OutputSegment.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
#include "llvm/BinaryFormat/MachO.h"
+#include "llvm/Support/Parallel.h"
+
+#include "mach-o/compact_unwind_encoding.h"
+
+#include <numeric>
using namespace llvm;
using namespace llvm::MachO;
+using namespace llvm::support::endian;
using namespace lld;
using namespace lld::macho;
// advantage, achieving a 3-order-of-magnitude reduction in the
// number of entries.
//
-// * The __TEXT,__unwind_info format can accommodate up to 127 unique
-// encodings for the space-efficient compressed format. In practice,
-// fewer than a dozen unique encodings are used by C++ programs of
-// all sizes. Therefore, we don't even bother implementing the regular
-// non-compressed format. Time will tell if anyone in the field ever
-// overflows the 127-encodings limit.
-//
// Refer to the definition of unwind_info_section_header in
// compact_unwind_encoding.h for an overview of the format we are encoding
// here.
-// TODO(gkm): prune __eh_frame entries superseded by __unwind_info, PR50410
// TODO(gkm): how do we align the 2nd-level pages?
+// The offsets of various fields in the on-disk representation of each compact
+// unwind entry.
+struct CompactUnwindOffsets {
+ uint32_t functionAddress;
+ uint32_t functionLength;
+ uint32_t encoding;
+ uint32_t personality;
+ uint32_t lsda;
+
+ CompactUnwindOffsets(size_t wordSize) {
+ if (wordSize == 8)
+ init<uint64_t>();
+ else {
+ assert(wordSize == 4);
+ init<uint32_t>();
+ }
+ }
+
+private:
+ template <class Ptr> void init() {
+ functionAddress = offsetof(Layout<Ptr>, functionAddress);
+ functionLength = offsetof(Layout<Ptr>, functionLength);
+ encoding = offsetof(Layout<Ptr>, encoding);
+ personality = offsetof(Layout<Ptr>, personality);
+ lsda = offsetof(Layout<Ptr>, lsda);
+ }
+
+ template <class Ptr> struct Layout {
+ Ptr functionAddress;
+ uint32_t functionLength;
+ compact_unwind_encoding_t encoding;
+ Ptr personality;
+ Ptr lsda;
+ };
+};
+
+// LLD's internal representation of a compact unwind entry.
+struct CompactUnwindEntry {
+ uint64_t functionAddress;
+ uint32_t functionLength;
+ compact_unwind_encoding_t encoding;
+ Symbol *personality;
+ InputSection *lsda;
+};
+
using EncodingMap = DenseMap<compact_unwind_encoding_t, size_t>;
struct SecondLevelPage {
EncodingMap localEncodingIndexes;
};
-template <class Ptr>
+// UnwindInfoSectionImpl allows us to avoid cluttering our header file with a
+// lengthy definition of UnwindInfoSection.
class UnwindInfoSectionImpl final : public UnwindInfoSection {
public:
- void prepareRelocations(ConcatInputSection *) override;
- void addInput(ConcatInputSection *) override;
+ UnwindInfoSectionImpl() : cuOffsets(target->wordSize) {}
+ uint64_t getSize() const override { return unwindInfoSize; }
+ void prepare() override;
void finalize() override;
void writeTo(uint8_t *buf) const override;
private:
+ void prepareRelocations(ConcatInputSection *);
+ void relocateCompactUnwind(std::vector<CompactUnwindEntry> &);
+ void encodePersonalities();
+ Symbol *canonicalizePersonality(Symbol *);
+
+ uint64_t unwindInfoSize = 0;
+ std::vector<decltype(symbols)::value_type> symbolsVec;
+ CompactUnwindOffsets cuOffsets;
std::vector<std::pair<compact_unwind_encoding_t, size_t>> commonEncodings;
EncodingMap commonEncodingIndexes;
- // Indices of personality functions within the GOT.
- std::vector<uint32_t> personalities;
+ // The entries here will be in the same order as their originating symbols
+ // in symbolsVec.
+ std::vector<CompactUnwindEntry> cuEntries;
+ // Indices into the cuEntries vector.
+ std::vector<size_t> cuIndices;
+ std::vector<Symbol *> personalities;
SmallDenseMap<std::pair<InputSection *, uint64_t /* addend */>, Symbol *>
personalityTable;
- std::vector<unwind_info_section_header_lsda_index_entry> lsdaEntries;
- // Map of function offset (from the image base) to an index within the LSDA
- // array.
- DenseMap<uint32_t, uint32_t> functionToLsdaIndex;
- std::vector<CompactUnwindEntry<Ptr>> cuVector;
- std::vector<CompactUnwindEntry<Ptr> *> cuPtrVector;
+ // Indices into cuEntries for CUEs with a non-null LSDA.
+ std::vector<size_t> entriesWithLsda;
+ // Map of cuEntries index to an index within the LSDA array.
+ DenseMap<size_t, uint32_t> lsdaIndex;
std::vector<SecondLevelPage> secondLevelPages;
uint64_t level2PagesOffset = 0;
+ // The highest-address function plus its size. The unwinder needs this to
+ // determine the address range that is covered by unwind info.
+ uint64_t cueEndBoundary = 0;
};
UnwindInfoSection::UnwindInfoSection()
: SyntheticSection(segment_names::text, section_names::unwindInfo) {
align = 4;
- compactUnwindSection =
- make<ConcatOutputSection>(section_names::compactUnwind);
}
-void UnwindInfoSection::prepareRelocations() {
- for (ConcatInputSection *isec : compactUnwindSection->inputs)
- prepareRelocations(isec);
+// Record function symbols that may need entries emitted in __unwind_info, which
+// stores unwind data for address ranges.
+//
+// Note that if several adjacent functions have the same unwind encoding and
+// personality function and no LSDA, they share one unwind entry. For this to
+// work, functions without unwind info need explicit "no unwind info" unwind
+// entries -- else the unwinder would think they have the unwind info of the
+// closest function with unwind info right before in the image. Thus, we add
+// function symbols for each unique address regardless of whether they have
+// associated unwind info.
+void UnwindInfoSection::addSymbol(const Defined *d) {
+ if (d->unwindEntry)
+ allEntriesAreOmitted = false;
+ // We don't yet know the final output address of this symbol, but we know that
+ // they are uniquely determined by a combination of the isec and value, so
+ // we use that as the key here.
+ auto p = symbols.insert({{d->isec, d->value}, d});
+ // If we have multiple symbols at the same address, only one of them can have
+ // an associated unwind entry.
+ if (!p.second && d->unwindEntry) {
+ assert(p.first->second == d || !p.first->second->unwindEntry);
+ p.first->second = d;
+ }
}
-template <class Ptr>
-void UnwindInfoSectionImpl<Ptr>::addInput(ConcatInputSection *isec) {
- assert(isec->getSegName() == segment_names::ld &&
- isec->getName() == section_names::compactUnwind);
- isec->parent = compactUnwindSection;
- compactUnwindSection->addInput(isec);
+void UnwindInfoSectionImpl::prepare() {
+ // This iteration needs to be deterministic, since prepareRelocations may add
+ // entries to the GOT. Hence the use of a MapVector for
+ // UnwindInfoSection::symbols.
+ for (const Defined *d : make_second_range(symbols))
+ if (d->unwindEntry) {
+ if (d->unwindEntry->getName() == section_names::compactUnwind) {
+ prepareRelocations(d->unwindEntry);
+ } else {
+ // We don't have to add entries to the GOT here because FDEs have
+ // explicit GOT relocations, so Writer::scanRelocations() will add those
+ // GOT entries. However, we still need to canonicalize the personality
+ // pointers (like prepareRelocations() does for CU entries) in order
+ // to avoid overflowing the 3-personality limit.
+ FDE &fde = cast<ObjFile>(d->getFile())->fdes[d->unwindEntry];
+ fde.personality = canonicalizePersonality(fde.personality);
+ }
+ }
}
// Compact unwind relocations have different semantics, so we handle them in a
// rebase opcodes for __LD,__compact_unwind, because that section doesn't
// actually end up in the final binary. Second, personality pointers always
// reside in the GOT and must be treated specially.
-template <class Ptr>
-void UnwindInfoSectionImpl<Ptr>::prepareRelocations(ConcatInputSection *isec) {
+void UnwindInfoSectionImpl::prepareRelocations(ConcatInputSection *isec) {
assert(!isec->shouldOmitFromOutput() &&
"__compact_unwind section should not be omitted");
for (size_t i = 0; i < isec->relocs.size(); ++i) {
Reloc &r = isec->relocs[i];
assert(target->hasAttr(r.type, RelocAttrBits::UNSIGNED));
-
- if (r.offset % sizeof(CompactUnwindEntry<Ptr>) == 0) {
- InputSection *referentIsec;
- if (auto *isec = r.referent.dyn_cast<InputSection *>())
- referentIsec = isec;
- else
- referentIsec = cast<Defined>(r.referent.dyn_cast<Symbol *>())->isec;
-
- if (!cast<ConcatInputSection>(referentIsec)->shouldOmitFromOutput())
- allEntriesAreOmitted = false;
- continue;
- }
-
- if (r.offset % sizeof(CompactUnwindEntry<Ptr>) !=
- offsetof(CompactUnwindEntry<Ptr>, personality))
+ // Since compact unwind sections aren't part of the inputSections vector,
+ // they don't get canonicalized by scanRelocations(), so we have to do the
+ // canonicalization here.
+ if (auto *referentIsec = r.referent.dyn_cast<InputSection *>())
+ r.referent = referentIsec->canonical();
+
+ // Functions and LSDA entries always reside in the same object file as the
+ // compact unwind entries that references them, and thus appear as section
+ // relocs. There is no need to prepare them. We only prepare relocs for
+ // personality functions.
+ if (r.offset != cuOffsets.personality)
continue;
if (auto *s = r.referent.dyn_cast<Symbol *>()) {
+ // Personality functions are nearly always system-defined (e.g.,
+ // ___gxx_personality_v0 for C++) and relocated as dylib symbols. When an
+ // application provides its own personality function, it might be
+ // referenced by an extern Defined symbol reloc, or a local section reloc.
+ if (auto *defined = dyn_cast<Defined>(s)) {
+ // XXX(vyng) This is a special case for handling duplicate personality
+ // symbols. Note that LD64's behavior is a bit different and it is
+ // inconsistent with how symbol resolution usually work
+ //
+ // So we've decided not to follow it. Instead, simply pick the symbol
+ // with the same name from the symbol table to replace the local one.
+ //
+ // (See discussions/alternatives already considered on D107533)
+ if (!defined->isExternal())
+ if (Symbol *sym = symtab->find(defined->getName()))
+ if (!sym->isLazy())
+ r.referent = s = sym;
+ }
if (auto *undefined = dyn_cast<Undefined>(s)) {
- treatUndefinedSymbol(*undefined);
+ treatUndefinedSymbol(*undefined, isec, r.offset);
// treatUndefinedSymbol() can replace s with a DylibSymbol; re-check.
if (isa<Undefined>(s))
continue;
}
+
+ // Similar to canonicalizePersonality(), but we also register a GOT entry.
if (auto *defined = dyn_cast<Defined>(s)) {
// Check if we have created a synthetic symbol at the same address.
Symbol *&personality =
}
continue;
}
+
assert(isa<DylibSymbol>(s));
in.got->addEntry(s);
continue;
s = make<Defined>("<internal>", /*file=*/nullptr, referentIsec,
r.addend, /*size=*/0, /*isWeakDef=*/false,
/*isExternal=*/false, /*isPrivateExtern=*/false,
+ /*includeInSymtab=*/true,
/*isThumb=*/false, /*isReferencedDynamically=*/false,
/*noDeadStrip=*/false);
+ s->used = true;
in.got->addEntry(s);
}
r.referent = s;
}
}
-// Unwind info lives in __DATA, and finalization of __TEXT will occur before
-// finalization of __DATA. Moreover, the finalization of unwind info depends on
-// the exact addresses that it references. So it is safe for compact unwind to
-// reference addresses in __TEXT, but not addresses in any other segment.
-static ConcatInputSection *checkTextSegment(InputSection *isec) {
- if (isec->getSegName() != segment_names::text)
- error("compact unwind references address in " + toString(isec) +
- " which is not in segment __TEXT");
- // __text should always be a ConcatInputSection.
- return cast<ConcatInputSection>(isec);
+Symbol *UnwindInfoSectionImpl::canonicalizePersonality(Symbol *personality) {
+ if (auto *defined = dyn_cast_or_null<Defined>(personality)) {
+ // Check if we have created a synthetic symbol at the same address.
+ Symbol *&synth = personalityTable[{defined->isec, defined->value}];
+ if (synth == nullptr)
+ synth = defined;
+ else if (synth != defined)
+ return synth;
+ }
+ return personality;
}
-template <class Ptr>
-constexpr Ptr TombstoneValue = std::numeric_limits<Ptr>::max();
-
// We need to apply the relocations to the pre-link compact unwind section
// before converting it to post-link form. There should only be absolute
// relocations here: since we are not emitting the pre-link CU section, there
// is no source address to make a relative location meaningful.
-template <class Ptr>
-static void
-relocateCompactUnwind(ConcatOutputSection *compactUnwindSection,
- std::vector<CompactUnwindEntry<Ptr>> &cuVector) {
- for (const ConcatInputSection *isec : compactUnwindSection->inputs) {
- assert(isec->parent == compactUnwindSection);
-
- uint8_t *buf =
- reinterpret_cast<uint8_t *>(cuVector.data()) + isec->outSecOff;
- memcpy(buf, isec->data.data(), isec->data.size());
-
- for (const Reloc &r : isec->relocs) {
- uint64_t referentVA = TombstoneValue<Ptr>;
- if (auto *referentSym = r.referent.dyn_cast<Symbol *>()) {
- if (!isa<Undefined>(referentSym)) {
- if (auto *defined = dyn_cast<Defined>(referentSym))
- checkTextSegment(defined->isec);
- // At this point in the link, we may not yet know the final address of
- // the GOT, so we just encode the index. We make it a 1-based index so
- // that we can distinguish the null pointer case.
- referentVA = referentSym->gotIndex + 1;
- }
- } else {
- auto *referentIsec = r.referent.get<InputSection *>();
- ConcatInputSection *concatIsec = checkTextSegment(referentIsec);
- if (!concatIsec->shouldOmitFromOutput())
- referentVA = referentIsec->getVA(r.addend);
+void UnwindInfoSectionImpl::relocateCompactUnwind(
+ std::vector<CompactUnwindEntry> &cuEntries) {
+ parallelFor(0, symbolsVec.size(), [&](size_t i) {
+ CompactUnwindEntry &cu = cuEntries[i];
+ const Defined *d = symbolsVec[i].second;
+ cu.functionAddress = d->getVA();
+ if (!d->unwindEntry)
+ return;
+
+ // If we have DWARF unwind info, create a CU entry that points to it.
+ if (d->unwindEntry->getName() == section_names::ehFrame) {
+ cu.encoding = target->modeDwarfEncoding | d->unwindEntry->outSecOff;
+ const FDE &fde = cast<ObjFile>(d->getFile())->fdes[d->unwindEntry];
+ cu.functionLength = fde.funcLength;
+ cu.personality = fde.personality;
+ cu.lsda = fde.lsda;
+ return;
+ }
+
+ assert(d->unwindEntry->getName() == section_names::compactUnwind);
+
+ auto buf = reinterpret_cast<const uint8_t *>(d->unwindEntry->data.data()) -
+ target->wordSize;
+ cu.functionLength =
+ support::endian::read32le(buf + cuOffsets.functionLength);
+ cu.encoding = support::endian::read32le(buf + cuOffsets.encoding);
+ for (const Reloc &r : d->unwindEntry->relocs) {
+ if (r.offset == cuOffsets.personality) {
+ cu.personality = r.referent.get<Symbol *>();
+ } else if (r.offset == cuOffsets.lsda) {
+ if (auto *referentSym = r.referent.dyn_cast<Symbol *>())
+ cu.lsda = cast<Defined>(referentSym)->isec;
+ else
+ cu.lsda = r.referent.get<InputSection *>();
}
- writeAddress(buf + r.offset, referentVA, r.length);
}
- }
+ });
}
// There should only be a handful of unique personality pointers, so we can
// encode them as 2-bit indices into a small array.
-template <class Ptr>
-static void
-encodePersonalities(const std::vector<CompactUnwindEntry<Ptr> *> &cuPtrVector,
- std::vector<uint32_t> &personalities) {
- for (CompactUnwindEntry<Ptr> *cu : cuPtrVector) {
- if (cu->personality == 0)
+void UnwindInfoSectionImpl::encodePersonalities() {
+ for (size_t idx : cuIndices) {
+ CompactUnwindEntry &cu = cuEntries[idx];
+ if (cu.personality == nullptr)
continue;
// Linear search is fast enough for a small array.
- auto it = find(personalities, cu->personality);
+ auto it = find(personalities, cu.personality);
uint32_t personalityIndex; // 1-based index
if (it != personalities.end()) {
personalityIndex = std::distance(personalities.begin(), it) + 1;
} else {
- personalities.push_back(cu->personality);
+ personalities.push_back(cu.personality);
personalityIndex = personalities.size();
}
- cu->encoding |=
+ cu.encoding |=
personalityIndex << countTrailingZeros(
static_cast<compact_unwind_encoding_t>(UNWIND_PERSONALITY_MASK));
}
if (personalities.size() > 3)
- error("too many personalities (" + std::to_string(personalities.size()) +
+ error("too many personalities (" + Twine(personalities.size()) +
") for compact unwind to encode");
}
-// __unwind_info stores unwind data for address ranges. If several
-// adjacent functions have the same unwind encoding, LSDA, and personality
-// function, they share one unwind entry. For this to work, functions without
-// unwind info need explicit "no unwind info" unwind entries -- else the
-// unwinder would think they have the unwind info of the closest function
-// with unwind info right before in the image.
-template <class Ptr>
-static void addEntriesForFunctionsWithoutUnwindInfo(
- std::vector<CompactUnwindEntry<Ptr>> &cuVector) {
- DenseSet<Ptr> hasUnwindInfo;
- for (CompactUnwindEntry<Ptr> &cuEntry : cuVector)
- if (cuEntry.functionAddress != TombstoneValue<Ptr>)
- hasUnwindInfo.insert(cuEntry.functionAddress);
-
- // Add explicit "has no unwind info" entries for all global and local symbols
- // without unwind info.
- auto markNoUnwindInfo = [&cuVector, &hasUnwindInfo](const Defined *d) {
- if (d->isLive() && d->isec && isCodeSection(d->isec)) {
- Ptr ptr = d->getVA();
- if (!hasUnwindInfo.count(ptr))
- cuVector.push_back({ptr, 0, 0, 0, 0});
- }
- };
- for (Symbol *sym : symtab->getSymbols())
- if (auto *d = dyn_cast<Defined>(sym))
- markNoUnwindInfo(d);
- for (const InputFile *file : inputFiles)
- if (auto *objFile = dyn_cast<ObjFile>(file))
- for (Symbol *sym : objFile->symbols)
- if (auto *d = dyn_cast_or_null<Defined>(sym))
- if (!d->isExternal())
- markNoUnwindInfo(d);
-}
-
static bool canFoldEncoding(compact_unwind_encoding_t encoding) {
// From compact_unwind_encoding.h:
// UNWIND_X86_64_MODE_STACK_IND:
// of the unwind info's unwind address, two functions that have identical
// unwind info can't be folded if it's using this encoding since both
// entries need unique addresses.
- static_assert(UNWIND_X86_64_MODE_MASK == UNWIND_X86_MODE_MASK, "");
- static_assert(UNWIND_X86_64_MODE_STACK_IND == UNWIND_X86_MODE_STACK_IND, "");
+ static_assert(static_cast<uint32_t>(UNWIND_X86_64_MODE_STACK_IND) ==
+ static_cast<uint32_t>(UNWIND_X86_MODE_STACK_IND));
if ((target->cpuType == CPU_TYPE_X86_64 || target->cpuType == CPU_TYPE_X86) &&
- (encoding & UNWIND_X86_64_MODE_MASK) == UNWIND_X86_64_MODE_STACK_IND) {
+ (encoding & UNWIND_MODE_MASK) == UNWIND_X86_64_MODE_STACK_IND) {
// FIXME: Consider passing in the two function addresses and getting
// their two stack sizes off the `subq` and only returning false if they're
// actually different.
}
// Scan the __LD,__compact_unwind entries and compute the space needs of
-// __TEXT,__unwind_info and __TEXT,__eh_frame
-template <class Ptr> void UnwindInfoSectionImpl<Ptr>::finalize() {
- if (compactUnwindSection == nullptr)
+// __TEXT,__unwind_info and __TEXT,__eh_frame.
+void UnwindInfoSectionImpl::finalize() {
+ if (symbols.empty())
return;
// At this point, the address space for __TEXT,__text has been
// assigned, so we can relocate the __LD,__compact_unwind entries
// into a temporary buffer. Relocation is necessary in order to sort
// the CU entries by function address. Sorting is necessary so that
- // we can fold adjacent CU entries with identical
- // encoding+personality+lsda. Folding is necessary because it reduces
- // the number of CU entries by as much as 3 orders of magnitude!
- compactUnwindSection->finalize();
- assert(compactUnwindSection->getSize() % sizeof(CompactUnwindEntry<Ptr>) ==
- 0);
- size_t cuCount =
- compactUnwindSection->getSize() / sizeof(CompactUnwindEntry<Ptr>);
- cuVector.resize(cuCount);
- relocateCompactUnwind(compactUnwindSection, cuVector);
-
- addEntriesForFunctionsWithoutUnwindInfo(cuVector);
+ // we can fold adjacent CU entries with identical encoding+personality
+ // and without any LSDA. Folding is necessary because it reduces the
+ // number of CU entries by as much as 3 orders of magnitude!
+ cuEntries.resize(symbols.size());
+ // The "map" part of the symbols MapVector was only needed for deduplication
+ // in addSymbol(). Now that we are done adding, move the contents to a plain
+ // std::vector for indexed access.
+ symbolsVec = symbols.takeVector();
+ relocateCompactUnwind(cuEntries);
// Rather than sort & fold the 32-byte entries directly, we create a
- // vector of pointers to entries and sort & fold that instead.
- cuPtrVector.reserve(cuVector.size());
- for (CompactUnwindEntry<Ptr> &cuEntry : cuVector)
- cuPtrVector.emplace_back(&cuEntry);
- llvm::sort(cuPtrVector, [](const CompactUnwindEntry<Ptr> *a,
- const CompactUnwindEntry<Ptr> *b) {
- return a->functionAddress < b->functionAddress;
+ // vector of indices to entries and sort & fold that instead.
+ cuIndices.resize(cuEntries.size());
+ std::iota(cuIndices.begin(), cuIndices.end(), 0);
+ llvm::sort(cuIndices, [&](size_t a, size_t b) {
+ return cuEntries[a].functionAddress < cuEntries[b].functionAddress;
});
- // Dead-stripped functions get a functionAddress of TombstoneValue in
- // relocateCompactUnwind(). Filter them out here.
- // FIXME: This doesn't yet collect associated data like LSDAs kept
- // alive only by a now-removed CompactUnwindEntry or other comdat-like
- // data (`kindNoneGroupSubordinate*` in ld64).
- CompactUnwindEntry<Ptr> tombstone;
- tombstone.functionAddress = TombstoneValue<Ptr>;
- cuPtrVector.erase(
- std::lower_bound(cuPtrVector.begin(), cuPtrVector.end(), &tombstone,
- [](const CompactUnwindEntry<Ptr> *a,
- const CompactUnwindEntry<Ptr> *b) {
- return a->functionAddress < b->functionAddress;
- }),
- cuPtrVector.end());
-
- // If there are no entries left after adding explicit "no unwind info"
- // entries and removing entries for dead-stripped functions, don't write
- // an __unwind_info section at all.
- assert(allEntriesAreOmitted == cuPtrVector.empty());
- if (cuPtrVector.empty())
- return;
+ // Record the ending boundary before we fold the entries.
+ cueEndBoundary = cuEntries[cuIndices.back()].functionAddress +
+ cuEntries[cuIndices.back()].functionLength;
- // Fold adjacent entries with matching encoding+personality+lsda
- // We use three iterators on the same cuPtrVector to fold in-situ:
+ // Fold adjacent entries with matching encoding+personality and without LSDA
+ // We use three iterators on the same cuIndices to fold in-situ:
// (1) `foldBegin` is the first of a potential sequence of matching entries
// (2) `foldEnd` is the first non-matching entry after `foldBegin`.
// The semi-open interval [ foldBegin .. foldEnd ) contains a range
// entries that can be folded into a single entry and written to ...
// (3) `foldWrite`
- auto foldWrite = cuPtrVector.begin();
- for (auto foldBegin = cuPtrVector.begin(); foldBegin < cuPtrVector.end();) {
+ auto foldWrite = cuIndices.begin();
+ for (auto foldBegin = cuIndices.begin(); foldBegin < cuIndices.end();) {
auto foldEnd = foldBegin;
- while (++foldEnd < cuPtrVector.end() &&
- (*foldBegin)->encoding == (*foldEnd)->encoding &&
- (*foldBegin)->personality == (*foldEnd)->personality &&
- (*foldBegin)->lsda == (*foldEnd)->lsda &&
- canFoldEncoding((*foldEnd)->encoding))
+ // Common LSDA encodings (e.g. for C++ and Objective-C) contain offsets from
+ // a base address. The base address is normally not contained directly in
+ // the LSDA, and in that case, the personality function treats the starting
+ // address of the function (which is computed by the unwinder) as the base
+ // address and interprets the LSDA accordingly. The unwinder computes the
+ // starting address of a function as the address associated with its CU
+ // entry. For this reason, we cannot fold adjacent entries if they have an
+ // LSDA, because folding would make the unwinder compute the wrong starting
+ // address for the functions with the folded entries, which in turn would
+ // cause the personality function to misinterpret the LSDA for those
+ // functions. In the very rare case where the base address is encoded
+ // directly in the LSDA, two functions at different addresses would
+ // necessarily have different LSDAs, so their CU entries would not have been
+ // folded anyway.
+ while (++foldEnd < cuIndices.end() &&
+ cuEntries[*foldBegin].encoding == cuEntries[*foldEnd].encoding &&
+ !cuEntries[*foldBegin].lsda && !cuEntries[*foldEnd].lsda &&
+ // If we've gotten to this point, we don't have an LSDA, which should
+ // also imply that we don't have a personality function, since in all
+ // likelihood a personality function needs the LSDA to do anything
+ // useful. It can be technically valid to have a personality function
+ // and no LSDA though (e.g. the C++ personality __gxx_personality_v0
+ // is just a no-op without LSDA), so we still check for personality
+ // function equivalence to handle that case.
+ cuEntries[*foldBegin].personality ==
+ cuEntries[*foldEnd].personality &&
+ canFoldEncoding(cuEntries[*foldEnd].encoding))
;
*foldWrite++ = *foldBegin;
foldBegin = foldEnd;
}
- cuPtrVector.erase(foldWrite, cuPtrVector.end());
+ cuIndices.erase(foldWrite, cuIndices.end());
- encodePersonalities(cuPtrVector, personalities);
+ encodePersonalities();
// Count frequencies of the folded encodings
EncodingMap encodingFrequencies;
- for (const CompactUnwindEntry<Ptr> *cuPtrEntry : cuPtrVector)
- encodingFrequencies[cuPtrEntry->encoding]++;
+ for (size_t idx : cuIndices)
+ encodingFrequencies[cuEntries[idx].encoding]++;
// Make a vector of encodings, sorted by descending frequency
for (const auto &frequency : encodingFrequencies)
// and 127..255 references a local per-second-level-page table.
// First we try the compact format and determine how many entries fit.
// If more entries fit in the regular format, we use that.
- for (size_t i = 0; i < cuPtrVector.size();) {
+ for (size_t i = 0; i < cuIndices.size();) {
+ size_t idx = cuIndices[i];
secondLevelPages.emplace_back();
SecondLevelPage &page = secondLevelPages.back();
page.entryIndex = i;
- uintptr_t functionAddressMax =
- cuPtrVector[i]->functionAddress + COMPRESSED_ENTRY_FUNC_OFFSET_MASK;
+ uint64_t functionAddressMax =
+ cuEntries[idx].functionAddress + COMPRESSED_ENTRY_FUNC_OFFSET_MASK;
size_t n = commonEncodings.size();
size_t wordsRemaining =
SECOND_LEVEL_PAGE_WORDS -
sizeof(unwind_info_compressed_second_level_page_header) /
sizeof(uint32_t);
- while (wordsRemaining >= 1 && i < cuPtrVector.size()) {
- const CompactUnwindEntry<Ptr> *cuPtr = cuPtrVector[i];
+ while (wordsRemaining >= 1 && i < cuIndices.size()) {
+ idx = cuIndices[i];
+ const CompactUnwindEntry *cuPtr = &cuEntries[idx];
if (cuPtr->functionAddress >= functionAddressMax) {
break;
} else if (commonEncodingIndexes.count(cuPtr->encoding) ||
}
page.entryCount = i - page.entryIndex;
- // If this is not the final page, see if it's possible to fit more
- // entries by using the regular format. This can happen when there
- // are many unique encodings, and we we saturated the local
- // encoding table early.
- if (i < cuPtrVector.size() &&
+ // If this is not the final page, see if it's possible to fit more entries
+ // by using the regular format. This can happen when there are many unique
+ // encodings, and we saturated the local encoding table early.
+ if (i < cuIndices.size() &&
page.entryCount < REGULAR_SECOND_LEVEL_ENTRIES_MAX) {
page.kind = UNWIND_SECOND_LEVEL_REGULAR;
page.entryCount = std::min(REGULAR_SECOND_LEVEL_ENTRIES_MAX,
- cuPtrVector.size() - page.entryIndex);
+ cuIndices.size() - page.entryIndex);
i = page.entryIndex + page.entryCount;
} else {
page.kind = UNWIND_SECOND_LEVEL_COMPRESSED;
}
}
- for (const CompactUnwindEntry<Ptr> *cu : cuPtrVector) {
- uint32_t functionOffset = cu->functionAddress - in.header->addr;
- functionToLsdaIndex[functionOffset] = lsdaEntries.size();
- if (cu->lsda != 0)
- lsdaEntries.push_back(
- {functionOffset, static_cast<uint32_t>(cu->lsda - in.header->addr)});
+ for (size_t idx : cuIndices) {
+ lsdaIndex[idx] = entriesWithLsda.size();
+ if (cuEntries[idx].lsda)
+ entriesWithLsda.push_back(idx);
}
// compute size of __TEXT,__unwind_info section
- level2PagesOffset =
- sizeof(unwind_info_section_header) +
- commonEncodings.size() * sizeof(uint32_t) +
- personalities.size() * sizeof(uint32_t) +
- // The extra second-level-page entry is for the sentinel
- (secondLevelPages.size() + 1) *
- sizeof(unwind_info_section_header_index_entry) +
- lsdaEntries.size() * sizeof(unwind_info_section_header_lsda_index_entry);
+ level2PagesOffset = sizeof(unwind_info_section_header) +
+ commonEncodings.size() * sizeof(uint32_t) +
+ personalities.size() * sizeof(uint32_t) +
+ // The extra second-level-page entry is for the sentinel
+ (secondLevelPages.size() + 1) *
+ sizeof(unwind_info_section_header_index_entry) +
+ entriesWithLsda.size() *
+ sizeof(unwind_info_section_header_lsda_index_entry);
unwindInfoSize =
level2PagesOffset + secondLevelPages.size() * SECOND_LEVEL_PAGE_BYTES;
}
// All inputs are relocated and output addresses are known, so write!
-template <class Ptr>
-void UnwindInfoSectionImpl<Ptr>::writeTo(uint8_t *buf) const {
- assert(!cuPtrVector.empty() && "call only if there is unwind info");
+void UnwindInfoSectionImpl::writeTo(uint8_t *buf) const {
+ assert(!cuIndices.empty() && "call only if there is unwind info");
// section header
auto *uip = reinterpret_cast<unwind_info_section_header *>(buf);
*i32p++ = encoding.first;
// Personalities
- for (const uint32_t &personality : personalities)
- *i32p++ =
- in.got->addr + (personality - 1) * target->wordSize - in.header->addr;
+ for (const Symbol *personality : personalities)
+ *i32p++ = personality->getGotVA() - in.header->addr;
+
+ // FIXME: LD64 checks and warns aboutgaps or overlapse in cuEntries address
+ // ranges. We should do the same too
// Level-1 index
uint32_t lsdaOffset =
uint64_t l2PagesOffset = level2PagesOffset;
auto *iep = reinterpret_cast<unwind_info_section_header_index_entry *>(i32p);
for (const SecondLevelPage &page : secondLevelPages) {
- iep->functionOffset =
- cuPtrVector[page.entryIndex]->functionAddress - in.header->addr;
+ size_t idx = cuIndices[page.entryIndex];
+ iep->functionOffset = cuEntries[idx].functionAddress - in.header->addr;
iep->secondLevelPagesSectionOffset = l2PagesOffset;
iep->lsdaIndexArraySectionOffset =
- lsdaOffset + functionToLsdaIndex.lookup(iep->functionOffset) *
+ lsdaOffset + lsdaIndex.lookup(idx) *
sizeof(unwind_info_section_header_lsda_index_entry);
iep++;
l2PagesOffset += SECOND_LEVEL_PAGE_BYTES;
}
// Level-1 sentinel
- const CompactUnwindEntry<Ptr> &cuEnd = *cuPtrVector.back();
- assert(cuEnd.functionAddress != TombstoneValue<Ptr>);
- iep->functionOffset =
- cuEnd.functionAddress - in.header->addr + cuEnd.functionLength;
+ // XXX(vyng): Note that LD64 adds +1 here.
+ // Unsure whether it's a bug or it's their workaround for something else.
+ // See comments from https://reviews.llvm.org/D138320.
+ iep->functionOffset = cueEndBoundary - in.header->addr;
iep->secondLevelPagesSectionOffset = 0;
iep->lsdaIndexArraySectionOffset =
- lsdaOffset +
- lsdaEntries.size() * sizeof(unwind_info_section_header_lsda_index_entry);
+ lsdaOffset + entriesWithLsda.size() *
+ sizeof(unwind_info_section_header_lsda_index_entry);
iep++;
// LSDAs
- size_t lsdaBytes =
- lsdaEntries.size() * sizeof(unwind_info_section_header_lsda_index_entry);
- if (lsdaBytes > 0)
- memcpy(iep, lsdaEntries.data(), lsdaBytes);
+ auto *lep =
+ reinterpret_cast<unwind_info_section_header_lsda_index_entry *>(iep);
+ for (size_t idx : entriesWithLsda) {
+ const CompactUnwindEntry &cu = cuEntries[idx];
+ lep->lsdaOffset = cu.lsda->getVA(/*off=*/0) - in.header->addr;
+ lep->functionOffset = cu.functionAddress - in.header->addr;
+ lep++;
+ }
// Level-2 pages
- auto *pp = reinterpret_cast<uint32_t *>(reinterpret_cast<uint8_t *>(iep) +
- lsdaBytes);
+ auto *pp = reinterpret_cast<uint32_t *>(lep);
for (const SecondLevelPage &page : secondLevelPages) {
if (page.kind == UNWIND_SECOND_LEVEL_COMPRESSED) {
uintptr_t functionAddressBase =
- cuPtrVector[page.entryIndex]->functionAddress;
+ cuEntries[cuIndices[page.entryIndex]].functionAddress;
auto *p2p =
reinterpret_cast<unwind_info_compressed_second_level_page_header *>(
pp);
p2p->encodingsCount = page.localEncodings.size();
auto *ep = reinterpret_cast<uint32_t *>(&p2p[1]);
for (size_t i = 0; i < page.entryCount; i++) {
- const CompactUnwindEntry<Ptr> *cuep = cuPtrVector[page.entryIndex + i];
- auto it = commonEncodingIndexes.find(cuep->encoding);
+ const CompactUnwindEntry &cue =
+ cuEntries[cuIndices[page.entryIndex + i]];
+ auto it = commonEncodingIndexes.find(cue.encoding);
if (it == commonEncodingIndexes.end())
- it = page.localEncodingIndexes.find(cuep->encoding);
+ it = page.localEncodingIndexes.find(cue.encoding);
*ep++ = (it->second << COMPRESSED_ENTRY_FUNC_OFFSET_BITS) |
- (cuep->functionAddress - functionAddressBase);
+ (cue.functionAddress - functionAddressBase);
}
- if (page.localEncodings.size() != 0)
+ if (!page.localEncodings.empty())
memcpy(ep, page.localEncodings.data(),
page.localEncodings.size() * sizeof(uint32_t));
} else {
p2p->entryCount = page.entryCount;
auto *ep = reinterpret_cast<uint32_t *>(&p2p[1]);
for (size_t i = 0; i < page.entryCount; i++) {
- const CompactUnwindEntry<Ptr> *cuep = cuPtrVector[page.entryIndex + i];
- *ep++ = cuep->functionAddress;
- *ep++ = cuep->encoding;
+ const CompactUnwindEntry &cue =
+ cuEntries[cuIndices[page.entryIndex + i]];
+ *ep++ = cue.functionAddress;
+ *ep++ = cue.encoding;
}
}
pp += SECOND_LEVEL_PAGE_WORDS;
}
UnwindInfoSection *macho::makeUnwindInfoSection() {
- if (target->wordSize == 8)
- return make<UnwindInfoSectionImpl<uint64_t>>();
- else
- return make<UnwindInfoSectionImpl<uint32_t>>();
+ return make<UnwindInfoSectionImpl>();
}
#include "ConcatOutputSection.h"
#include "SyntheticSections.h"
+#include "llvm/ADT/MapVector.h"
-#include "mach-o/compact_unwind_encoding.h"
-
-namespace lld {
-namespace macho {
-
-template <class Ptr> struct CompactUnwindEntry {
- Ptr functionAddress;
- uint32_t functionLength;
- compact_unwind_encoding_t encoding;
- Ptr personality;
- Ptr lsda;
-};
+namespace lld::macho {
class UnwindInfoSection : public SyntheticSection {
public:
- bool isNeeded() const override {
- return !compactUnwindSection->inputs.empty() && !allEntriesAreOmitted;
- }
- uint64_t getSize() const override { return unwindInfoSize; }
- virtual void addInput(ConcatInputSection *) = 0;
- std::vector<ConcatInputSection *> getInputs() {
- return compactUnwindSection->inputs;
- }
- void prepareRelocations();
+ // If all functions are free of unwind info, we can omit the unwind info
+ // section entirely.
+ bool isNeeded() const override { return !allEntriesAreOmitted; }
+ void addSymbol(const Defined *);
+ virtual void prepare() = 0;
protected:
UnwindInfoSection();
- virtual void prepareRelocations(ConcatInputSection *) = 0;
- ConcatOutputSection *compactUnwindSection;
- uint64_t unwindInfoSize = 0;
+ llvm::MapVector<std::pair<const InputSection *, uint64_t /*Defined::value*/>,
+ const Defined *>
+ symbols;
bool allEntriesAreOmitted = true;
};
UnwindInfoSection *makeUnwindInfoSection();
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
#include "MapFile.h"
#include "OutputSection.h"
#include "OutputSegment.h"
+#include "SectionPriorities.h"
#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "UnwindInfoSection.h"
#include "lld/Common/Arrays.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "llvm/BinaryFormat/MachO.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/LEB128.h"
-#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/ThreadPool.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/xxhash.h"
void openFile();
void writeSections();
+ void applyOptimizationHints();
+ void buildFixupChains();
void writeUuid();
void writeCodeSignature();
void writeOutputFile();
template <class LP> void run();
+ ThreadPool threadPool;
std::unique_ptr<FileOutputBuffer> &buffer;
uint64_t addr = 0;
uint64_t fileOff = 0;
LCSubFramework(StringRef umbrella) : umbrella(umbrella) {}
uint32_t getSize() const override {
- return alignTo(sizeof(sub_framework_command) + umbrella.size() + 1,
- target->wordSize);
+ return alignToPowerOf2(sizeof(sub_framework_command) + umbrella.size() + 1,
+ target->wordSize);
}
void writeTo(uint8_t *buf) const override {
void writeTo(uint8_t *buf) const override {
using SegmentCommand = typename LP::segment_command;
- using Section = typename LP::section;
+ using SectionHeader = typename LP::section;
auto *c = reinterpret_cast<SegmentCommand *>(buf);
buf += sizeof(SegmentCommand);
c->vmsize = seg->vmSize;
c->filesize = seg->fileSize;
c->nsects = seg->numNonHiddenSections();
+ c->flags = seg->flags;
for (const OutputSection *osec : seg->getSections()) {
if (osec->isHidden())
continue;
- auto *sectHdr = reinterpret_cast<Section *>(buf);
- buf += sizeof(Section);
+ auto *sectHdr = reinterpret_cast<SectionHeader *>(buf);
+ buf += sizeof(SectionHeader);
memcpy(sectHdr->sectname, osec->name.data(), osec->name.size());
memcpy(sectHdr->segname, name.data(), name.size());
}
uint32_t getSize() const override {
- return alignTo(sizeof(dylib_command) + path.size() + 1, 8);
+ return alignToPowerOf2(sizeof(dylib_command) + path.size() + 1,
+ target->wordSize);
}
void writeTo(uint8_t *buf) const override {
}
static uint32_t getInstanceCount() { return instanceCount; }
+ static void resetInstanceCount() { instanceCount = 0; }
private:
LoadCommandType type;
class LCLoadDylinker final : public LoadCommand {
public:
uint32_t getSize() const override {
- return alignTo(sizeof(dylinker_command) + path.size() + 1, 8);
+ return alignToPowerOf2(sizeof(dylinker_command) + path.size() + 1,
+ target->wordSize);
}
void writeTo(uint8_t *buf) const override {
explicit LCRPath(StringRef path) : path(path) {}
uint32_t getSize() const override {
- return alignTo(sizeof(rpath_command) + path.size() + 1, target->wordSize);
+ return alignToPowerOf2(sizeof(rpath_command) + path.size() + 1,
+ target->wordSize);
}
void writeTo(uint8_t *buf) const override {
StringRef path;
};
+class LCDyldEnv final : public LoadCommand {
+public:
+ explicit LCDyldEnv(StringRef name) : name(name) {}
+
+ uint32_t getSize() const override {
+ return alignToPowerOf2(sizeof(dyld_env_command) + name.size() + 1,
+ target->wordSize);
+ }
+
+ void writeTo(uint8_t *buf) const override {
+ auto *c = reinterpret_cast<dyld_env_command *>(buf);
+ buf += sizeof(dyld_env_command);
+
+ c->cmd = LC_DYLD_ENVIRONMENT;
+ c->cmdsize = getSize();
+ c->name = sizeof(dyld_env_command);
+
+ memcpy(buf, name.data(), name.size());
+ buf[name.size()] = '\0';
+ }
+
+private:
+ StringRef name;
+};
+
class LCMinVersion final : public LoadCommand {
public:
explicit LCMinVersion(const PlatformInfo &platformInfo)
void writeTo(uint8_t *buf) const override {
auto *c = reinterpret_cast<version_min_command *>(buf);
switch (platformInfo.target.Platform) {
- case PlatformKind::macOS:
+ case PLATFORM_MACOS:
c->cmd = LC_VERSION_MIN_MACOSX;
break;
- case PlatformKind::iOS:
- case PlatformKind::iOSSimulator:
+ case PLATFORM_IOS:
+ case PLATFORM_IOSSIMULATOR:
c->cmd = LC_VERSION_MIN_IPHONEOS;
break;
- case PlatformKind::tvOS:
- case PlatformKind::tvOSSimulator:
+ case PLATFORM_TVOS:
+ case PLATFORM_TVOSSIMULATOR:
c->cmd = LC_VERSION_MIN_TVOS;
break;
- case PlatformKind::watchOS:
- case PlatformKind::watchOSSimulator:
+ case PLATFORM_WATCHOS:
+ case PLATFORM_WATCHOSSIMULATOR:
c->cmd = LC_VERSION_MIN_WATCHOS;
break;
default:
auto *c = reinterpret_cast<build_version_command *>(buf);
c->cmd = LC_BUILD_VERSION;
c->cmdsize = getSize();
+
c->platform = static_cast<uint32_t>(platformInfo.target.Platform);
c->minos = encodeVersion(platformInfo.minimum);
c->sdk = encodeVersion(platformInfo.sdk);
+
c->ntools = ntools;
auto *t = reinterpret_cast<build_tool_version *>(&c[1]);
t->tool = TOOL_LD;
CodeSignatureSection *section;
};
+class LCExportsTrie final : public LoadCommand {
+public:
+ LCExportsTrie(ExportSection *section) : section(section) {}
+
+ uint32_t getSize() const override { return sizeof(linkedit_data_command); }
+
+ void writeTo(uint8_t *buf) const override {
+ auto *c = reinterpret_cast<linkedit_data_command *>(buf);
+ c->cmd = LC_DYLD_EXPORTS_TRIE;
+ c->cmdsize = getSize();
+ c->dataoff = section->fileOff;
+ c->datasize = section->getSize();
+ }
+
+ ExportSection *section;
+};
+
+class LCChainedFixups final : public LoadCommand {
+public:
+ LCChainedFixups(ChainedFixupsSection *section) : section(section) {}
+
+ uint32_t getSize() const override { return sizeof(linkedit_data_command); }
+
+ void writeTo(uint8_t *buf) const override {
+ auto *c = reinterpret_cast<linkedit_data_command *>(buf);
+ c->cmd = LC_DYLD_CHAINED_FIXUPS;
+ c->cmdsize = getSize();
+ c->dataoff = section->fileOff;
+ c->datasize = section->getSize();
+ }
+
+ ChainedFixupsSection *section;
+};
+
} // namespace
void Writer::treatSpecialUndefineds() {
}
}
-// Add stubs and bindings where necessary (e.g. if the symbol is a
-// DylibSymbol.)
-static void prepareBranchTarget(Symbol *sym) {
- if (auto *dysym = dyn_cast<DylibSymbol>(sym)) {
- if (in.stubs->addEntry(dysym)) {
- if (sym->isWeakDef()) {
- in.binding->addEntry(dysym, in.lazyPointers->isec,
- sym->stubsIndex * target->wordSize);
- in.weakBinding->addEntry(sym, in.lazyPointers->isec,
- sym->stubsIndex * target->wordSize);
- } else {
- in.lazyBinding->addEntry(dysym);
- }
- }
- } else if (auto *defined = dyn_cast<Defined>(sym)) {
- if (defined->isExternalWeakDef()) {
- if (in.stubs->addEntry(sym)) {
- in.rebase->addEntry(in.lazyPointers->isec,
- sym->stubsIndex * target->wordSize);
- in.weakBinding->addEntry(sym, in.lazyPointers->isec,
- sym->stubsIndex * target->wordSize);
- }
- }
- } else {
- llvm_unreachable("invalid branch target symbol type");
- }
-}
-
-// Can a symbol's address can only be resolved at runtime?
-static bool needsBinding(const Symbol *sym) {
- if (isa<DylibSymbol>(sym))
- return true;
- if (const auto *defined = dyn_cast<Defined>(sym))
- return defined->isExternalWeakDef();
- return false;
-}
-
static void prepareSymbolRelocation(Symbol *sym, const InputSection *isec,
- const Reloc &r) {
+ const lld::macho::Reloc &r) {
assert(sym->isLive());
const RelocAttrs &relocAttrs = target->getRelocAttrs(r.type);
if (relocAttrs.hasAttr(RelocAttrBits::BRANCH)) {
- prepareBranchTarget(sym);
+ if (needsBinding(sym))
+ in.stubs->addEntry(sym);
} else if (relocAttrs.hasAttr(RelocAttrBits::GOT)) {
if (relocAttrs.hasAttr(RelocAttrBits::POINTER) || needsBinding(sym))
in.got->addEntry(sym);
continue;
for (auto it = isec->relocs.begin(); it != isec->relocs.end(); ++it) {
- Reloc &r = *it;
+ lld::macho::Reloc &r = *it;
if (target->hasAttr(r.type, RelocAttrBits::SUBTRAHEND)) {
// Skip over the following UNSIGNED relocation -- it's just there as the
// minuend, and doesn't have the usual UNSIGNED semantics. We don't want
}
if (auto *sym = r.referent.dyn_cast<Symbol *>()) {
if (auto *undefined = dyn_cast<Undefined>(sym))
- treatUndefinedSymbol(*undefined);
+ treatUndefinedSymbol(*undefined, isec, r.offset);
// treatUndefinedSymbol() can replace sym with a DylibSymbol; re-check.
if (!isa<Undefined>(sym) && validateSymbolRelocation(sym, isec, r))
prepareSymbolRelocation(sym, isec, r);
// too...
auto *referentIsec = r.referent.get<InputSection *>();
r.referent = referentIsec->canonical();
- if (!r.pcrel)
- in.rebase->addEntry(isec, r.offset);
+ if (!r.pcrel) {
+ if (config->emitChainedFixups)
+ in.chainedFixups->addRebase(isec, r.offset);
+ else
+ in.rebase->addEntry(isec, r.offset);
+ }
}
}
}
- in.unwindInfo->prepareRelocations();
+ in.unwindInfo->prepare();
+}
+
+static void addNonWeakDefinition(const Defined *defined) {
+ if (config->emitChainedFixups)
+ in.chainedFixups->setHasNonWeakDefinition();
+ else
+ in.weakBinding->addNonWeakDefinition(defined);
}
void Writer::scanSymbols() {
TimeTraceScope timeScope("Scan symbols");
- for (const Symbol *sym : symtab->getSymbols()) {
- if (const auto *defined = dyn_cast<Defined>(sym)) {
- if (defined->overridesWeakDef && defined->isLive())
- in.weakBinding->addNonWeakDefinition(defined);
+ for (Symbol *sym : symtab->getSymbols()) {
+ if (auto *defined = dyn_cast<Defined>(sym)) {
+ if (!defined->isLive())
+ continue;
+ defined->canonicalize();
+ if (defined->overridesWeakDef)
+ addNonWeakDefinition(defined);
+ if (!defined->isAbsolute() && isCodeSection(defined->isec))
+ in.unwindInfo->addSymbol(defined);
} else if (const auto *dysym = dyn_cast<DylibSymbol>(sym)) {
// This branch intentionally doesn't check isLive().
if (dysym->isDynamicLookup())
continue;
dysym->getFile()->refState =
std::max(dysym->getFile()->refState, dysym->getRefState());
+ } else if (isa<Undefined>(sym)) {
+ if (sym->getName().startswith(ObjCStubsSection::symbolPrefix))
+ in.objcStubs->addEntry(sym);
}
}
+
+ for (const InputFile *file : inputFiles) {
+ if (auto *objFile = dyn_cast<ObjFile>(file))
+ for (Symbol *sym : objFile->symbols) {
+ if (auto *defined = dyn_cast_or_null<Defined>(sym)) {
+ if (!defined->isLive())
+ continue;
+ defined->canonicalize();
+ if (!defined->isExternal() && !defined->isAbsolute() &&
+ isCodeSection(defined->isec))
+ in.unwindInfo->addSymbol(defined);
+ }
+ }
+ }
}
// TODO: ld64 enforces the old load commands in a few other cases.
static bool useLCBuildVersion(const PlatformInfo &platformInfo) {
- static const std::vector<std::pair<PlatformKind, VersionTuple>> minVersion = {
- {PlatformKind::macOS, VersionTuple(10, 14)},
- {PlatformKind::iOS, VersionTuple(12, 0)},
- {PlatformKind::iOSSimulator, VersionTuple(13, 0)},
- {PlatformKind::tvOS, VersionTuple(12, 0)},
- {PlatformKind::tvOSSimulator, VersionTuple(13, 0)},
- {PlatformKind::watchOS, VersionTuple(5, 0)},
- {PlatformKind::watchOSSimulator, VersionTuple(6, 0)}};
+ static const std::array<std::pair<PlatformType, VersionTuple>, 7> minVersion =
+ {{{PLATFORM_MACOS, VersionTuple(10, 14)},
+ {PLATFORM_IOS, VersionTuple(12, 0)},
+ {PLATFORM_IOSSIMULATOR, VersionTuple(13, 0)},
+ {PLATFORM_TVOS, VersionTuple(12, 0)},
+ {PLATFORM_TVOSSIMULATOR, VersionTuple(13, 0)},
+ {PLATFORM_WATCHOS, VersionTuple(5, 0)},
+ {PLATFORM_WATCHOSSIMULATOR, VersionTuple(6, 0)}}};
auto it = llvm::find_if(minVersion, [&](const auto &p) {
return p.first == platformInfo.target.Platform;
});
seg->index = segIndex++;
}
- in.header->addLoadCommand(make<LCDyldInfo>(
- in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports));
+ if (config->emitChainedFixups) {
+ in.header->addLoadCommand(make<LCChainedFixups>(in.chainedFixups));
+ in.header->addLoadCommand(make<LCExportsTrie>(in.exports));
+ } else {
+ in.header->addLoadCommand(make<LCDyldInfo>(
+ in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports));
+ }
in.header->addLoadCommand(make<LCSymtab>(symtabSection, stringTableSection));
in.header->addLoadCommand(
make<LCDysymtab>(symtabSection, indirectSymtabSection));
else
in.header->addLoadCommand(make<LCMinVersion>(config->platformInfo));
+ if (config->secondaryPlatformInfo) {
+ in.header->addLoadCommand(
+ make<LCBuildVersion>(*config->secondaryPlatformInfo));
+ }
+
// This is down here to match ld64's load command order.
if (config->outputType == MH_EXECUTE)
in.header->addLoadCommand(make<LCMain>());
+ // See ld64's OutputFile::buildDylibOrdinalMapping for the corresponding
+ // library ordinal computation code in ld64.
int64_t dylibOrdinal = 1;
DenseMap<StringRef, int64_t> ordinalForInstallName;
+
+ std::vector<DylibFile *> dylibFiles;
for (InputFile *file : inputFiles) {
- if (auto *dylibFile = dyn_cast<DylibFile>(file)) {
- if (dylibFile->isBundleLoader) {
- dylibFile->ordinal = BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
- // Shortcut since bundle-loader does not re-export the symbols.
+ if (auto *dylibFile = dyn_cast<DylibFile>(file))
+ dylibFiles.push_back(dylibFile);
+ }
+ for (size_t i = 0; i < dylibFiles.size(); ++i)
+ dylibFiles.insert(dylibFiles.end(), dylibFiles[i]->extraDylibs.begin(),
+ dylibFiles[i]->extraDylibs.end());
- dylibFile->reexport = false;
- continue;
- }
+ for (DylibFile *dylibFile : dylibFiles) {
+ if (dylibFile->isBundleLoader) {
+ dylibFile->ordinal = BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
+ // Shortcut since bundle-loader does not re-export the symbols.
- // Don't emit load commands for a dylib that is not referenced if:
- // - it was added implicitly (via a reexport, an LC_LOAD_DYLINKER --
- // if it's on the linker command line, it's explicit)
- // - or it's marked MH_DEAD_STRIPPABLE_DYLIB
- // - or the flag -dead_strip_dylibs is used
- // FIXME: `isReferenced()` is currently computed before dead code
- // stripping, so references from dead code keep a dylib alive. This
- // matches ld64, but it's something we should do better.
- if (!dylibFile->isReferenced() && !dylibFile->forceNeeded &&
- (!dylibFile->explicitlyLinked || dylibFile->deadStrippable ||
- config->deadStripDylibs))
- continue;
+ dylibFile->reexport = false;
+ continue;
+ }
- // Several DylibFiles can have the same installName. Only emit a single
- // load command for that installName and give all these DylibFiles the
- // same ordinal.
- // This can happen in several cases:
- // - a new framework could change its installName to an older
- // framework name via an $ld$ symbol depending on platform_version
- // - symlinks (for example, libpthread.tbd is a symlink to libSystem.tbd;
- // Foo.framework/Foo.tbd is usually a symlink to
- // Foo.framework/Versions/Current/Foo.tbd, where
- // Foo.framework/Versions/Current is usually a symlink to
- // Foo.framework/Versions/A)
- // - a framework can be linked both explicitly on the linker
- // command line and implicitly as a reexport from a different
- // framework. The re-export will usually point to the tbd file
- // in Foo.framework/Versions/A/Foo.tbd, while the explicit link will
- // usually find Foo.framework/Foo.tbd. These are usually symlinks,
- // but in a --reproduce archive they will be identical but distinct
- // files.
- // In the first case, *semantically distinct* DylibFiles will have the
- // same installName.
- int64_t &ordinal = ordinalForInstallName[dylibFile->installName];
- if (ordinal) {
- dylibFile->ordinal = ordinal;
- continue;
- }
+ // Don't emit load commands for a dylib that is not referenced if:
+ // - it was added implicitly (via a reexport, an LC_LOAD_DYLINKER --
+ // if it's on the linker command line, it's explicit)
+ // - or it's marked MH_DEAD_STRIPPABLE_DYLIB
+ // - or the flag -dead_strip_dylibs is used
+ // FIXME: `isReferenced()` is currently computed before dead code
+ // stripping, so references from dead code keep a dylib alive. This
+ // matches ld64, but it's something we should do better.
+ if (!dylibFile->isReferenced() && !dylibFile->forceNeeded &&
+ (!dylibFile->isExplicitlyLinked() || dylibFile->deadStrippable ||
+ config->deadStripDylibs))
+ continue;
- ordinal = dylibFile->ordinal = dylibOrdinal++;
- LoadCommandType lcType =
- dylibFile->forceWeakImport || dylibFile->refState == RefState::Weak
- ? LC_LOAD_WEAK_DYLIB
- : LC_LOAD_DYLIB;
- in.header->addLoadCommand(make<LCDylib>(lcType, dylibFile->installName,
- dylibFile->compatibilityVersion,
- dylibFile->currentVersion));
-
- if (dylibFile->reexport)
- in.header->addLoadCommand(
- make<LCDylib>(LC_REEXPORT_DYLIB, dylibFile->installName));
+ // Several DylibFiles can have the same installName. Only emit a single
+ // load command for that installName and give all these DylibFiles the
+ // same ordinal.
+ // This can happen in several cases:
+ // - a new framework could change its installName to an older
+ // framework name via an $ld$ symbol depending on platform_version
+ // - symlinks (for example, libpthread.tbd is a symlink to libSystem.tbd;
+ // Foo.framework/Foo.tbd is usually a symlink to
+ // Foo.framework/Versions/Current/Foo.tbd, where
+ // Foo.framework/Versions/Current is usually a symlink to
+ // Foo.framework/Versions/A)
+ // - a framework can be linked both explicitly on the linker
+ // command line and implicitly as a reexport from a different
+ // framework. The re-export will usually point to the tbd file
+ // in Foo.framework/Versions/A/Foo.tbd, while the explicit link will
+ // usually find Foo.framework/Foo.tbd. These are usually symlinks,
+ // but in a --reproduce archive they will be identical but distinct
+ // files.
+ // In the first case, *semantically distinct* DylibFiles will have the
+ // same installName.
+ int64_t &ordinal = ordinalForInstallName[dylibFile->installName];
+ if (ordinal) {
+ dylibFile->ordinal = ordinal;
+ continue;
}
+
+ ordinal = dylibFile->ordinal = dylibOrdinal++;
+ LoadCommandType lcType =
+ dylibFile->forceWeakImport || dylibFile->refState == RefState::Weak
+ ? LC_LOAD_WEAK_DYLIB
+ : LC_LOAD_DYLIB;
+ in.header->addLoadCommand(make<LCDylib>(lcType, dylibFile->installName,
+ dylibFile->compatibilityVersion,
+ dylibFile->currentVersion));
+
+ if (dylibFile->reexport)
+ in.header->addLoadCommand(
+ make<LCDylib>(LC_REEXPORT_DYLIB, dylibFile->installName));
}
+ for (const auto &dyldEnv : config->dyldEnvs)
+ in.header->addLoadCommand(make<LCDyldEnv>(dyldEnv));
+
if (functionStartsSection)
in.header->addLoadCommand(make<LCFunctionStarts>(functionStartsSection));
if (dataInCodeSection)
: 0));
}
-static size_t getSymbolPriority(const SymbolPriorityEntry &entry,
- const InputFile *f) {
- // We don't use toString(InputFile *) here because it returns the full path
- // for object files, and we only want the basename.
- StringRef filename;
- if (f->archiveName.empty())
- filename = path::filename(f->getName());
- else
- filename = saver.save(path::filename(f->archiveName) + "(" +
- path::filename(f->getName()) + ")");
- return std::max(entry.objectFiles.lookup(filename), entry.anyObjectFile);
-}
-
-// Each section gets assigned the priority of the highest-priority symbol it
-// contains.
-static DenseMap<const InputSection *, size_t> buildInputSectionPriorities() {
- DenseMap<const InputSection *, size_t> sectionPriorities;
-
- if (config->priorities.empty())
- return sectionPriorities;
-
- auto addSym = [&](Defined &sym) {
- if (sym.isAbsolute())
- return;
-
- auto it = config->priorities.find(sym.getName());
- if (it == config->priorities.end())
- return;
-
- SymbolPriorityEntry &entry = it->second;
- size_t &priority = sectionPriorities[sym.isec];
- priority =
- std::max(priority, getSymbolPriority(entry, sym.isec->getFile()));
- };
-
- // TODO: Make sure this handles weak symbols correctly.
- for (const InputFile *file : inputFiles) {
- if (isa<ObjFile>(file))
- for (Symbol *sym : file->symbols)
- if (auto *d = dyn_cast_or_null<Defined>(sym))
- addSym(*d);
- }
-
- return sectionPriorities;
-}
-
// Sorting only can happen once all outputs have been collected. Here we sort
// segments, output sections within each segment, and input sections within each
// output segment.
sortOutputSegments();
DenseMap<const InputSection *, size_t> isecPriorities =
- buildInputSectionPriorities();
+ priorityBuilder.buildInputSectionPriorities();
uint32_t sectionIndex = 0;
for (OutputSegment *seg : outputSegments) {
seg->sortOutputSections();
+ // References from thread-local variable sections are treated as offsets
+ // relative to the start of the thread-local data memory area, which
+ // is initialized via copying all the TLV data sections (which are all
+ // contiguous). If later data sections require a greater alignment than
+ // earlier ones, the offsets of data within those sections won't be
+ // guaranteed to aligned unless we normalize alignments. We therefore use
+ // the largest alignment for all TLV data sections.
+ uint32_t tlvAlign = 0;
+ for (const OutputSection *osec : seg->getSections())
+ if (isThreadLocalData(osec->flags) && osec->align > tlvAlign)
+ tlvAlign = osec->align;
+
for (OutputSection *osec : seg->getSections()) {
// Now that the output sections are sorted, assign the final
// output section indices.
if (!osec->isHidden())
osec->index = ++sectionIndex;
- if (!firstTLVDataSection && isThreadLocalData(osec->flags))
- firstTLVDataSection = osec;
+ if (isThreadLocalData(osec->flags)) {
+ if (!firstTLVDataSection)
+ firstTLVDataSection = osec;
+ osec->align = tlvAlign;
+ }
if (!isecPriorities.empty()) {
if (auto *merged = dyn_cast<ConcatOutputSection>(osec)) {
- llvm::stable_sort(merged->inputs,
- [&](InputSection *a, InputSection *b) {
- return isecPriorities[a] > isecPriorities[b];
- });
+ llvm::stable_sort(
+ merged->inputs, [&](InputSection *a, InputSection *b) {
+ return isecPriorities.lookup(a) > isecPriorities.lookup(b);
+ });
}
}
}
StringRef segname = it.first.first;
ConcatOutputSection *osec = it.second;
assert(segname != segment_names::ld);
- if (osec->isNeeded())
+ if (osec->isNeeded()) {
+ // See comment in ObjFile::splitEhFrames()
+ if (osec->name == section_names::ehFrame &&
+ segname == segment_names::text)
+ osec->align = target->wordSize;
+
+ // MC keeps the default 1-byte alignment for __thread_vars, even though it
+ // contains pointers that are fixed up by dyld, which requires proper
+ // alignment.
+ if (isThreadLocalVariables(osec->flags))
+ osec->align = std::max<uint32_t>(osec->align, target->wordSize);
+
getOrCreateOutputSegment(segname)->addOutputSection(osec);
+ }
}
for (SyntheticSection *ssec : syntheticSections) {
auto it = concatOutputSections.find({ssec->segname, ssec->name});
- if (ssec->isNeeded()) {
+ // We add all LinkEdit sections here because we don't know if they are
+ // needed until their finalizeContents() methods get called later. While
+ // this means that we add some redundant sections to __LINKEDIT, there is
+ // is no redundancy in the output, as we do not emit section headers for
+ // any LinkEdit sections.
+ if (ssec->isNeeded() || ssec->segname == segment_names::linkEdit) {
if (it == concatOutputSections.end()) {
getOrCreateOutputSegment(ssec->segname)->addOutputSection(ssec);
} else {
void Writer::finalizeAddresses() {
TimeTraceScope timeScope("Finalize addresses");
uint64_t pageSize = target->getPageSize();
+
+ // We could parallelize this loop, but local benchmarking indicates it is
+ // faster to do it all in the main thread.
+ for (OutputSegment *seg : outputSegments) {
+ if (seg == linkEditSegment)
+ continue;
+ for (OutputSection *osec : seg->getSections()) {
+ if (!osec->isNeeded())
+ continue;
+ // Other kinds of OutputSections have already been finalized.
+ if (auto concatOsec = dyn_cast<ConcatOutputSection>(osec))
+ concatOsec->finalizeContents();
+ }
+ }
+
// Ensure that segments (and the sections they contain) are allocated
// addresses in ascending order, which dyld requires.
//
// `fileOff + fileSize == next segment fileOff`. So we call alignTo() before
// (instead of after) computing fileSize to ensure that the segments are
// contiguous. We handle addr / vmSize similarly for the same reason.
- fileOff = alignTo(fileOff, pageSize);
- addr = alignTo(addr, pageSize);
+ fileOff = alignToPowerOf2(fileOff, pageSize);
+ addr = alignToPowerOf2(addr, pageSize);
seg->vmSize = addr - seg->addr;
seg->fileSize = fileOff - seg->fileOff;
seg->assignAddressesToStartEndSymbols();
void Writer::finalizeLinkEditSegment() {
TimeTraceScope timeScope("Finalize __LINKEDIT segment");
// Fill __LINKEDIT contents.
- std::vector<LinkEditSection *> linkEditSections{
- in.rebase,
- in.binding,
- in.weakBinding,
- in.lazyBinding,
- in.exports,
- symtabSection,
- indirectSymtabSection,
- dataInCodeSection,
- functionStartsSection,
+ std::array<LinkEditSection *, 10> linkEditSections{
+ in.rebase, in.binding,
+ in.weakBinding, in.lazyBinding,
+ in.exports, in.chainedFixups,
+ symtabSection, indirectSymtabSection,
+ dataInCodeSection, functionStartsSection,
};
- parallelForEach(linkEditSections, [](LinkEditSection *osec) {
+ SmallVector<std::shared_future<void>> threadFutures;
+ threadFutures.reserve(linkEditSections.size());
+ for (LinkEditSection *osec : linkEditSections)
if (osec)
- osec->finalizeContents();
- });
+ threadFutures.emplace_back(threadPool.async(
+ [](LinkEditSection *osec) { osec->finalizeContents(); }, osec));
+ for (std::shared_future<void> &future : threadFutures)
+ future.wait();
// Now that __LINKEDIT is filled out, do a proper calculation of its
// addresses and offsets.
FileOutputBuffer::F_executable);
if (!bufferOrErr)
- error("failed to open " + config->outputFile + ": " +
+ fatal("failed to open " + config->outputFile + ": " +
llvm::toString(bufferOrErr.takeError()));
- else
- buffer = std::move(*bufferOrErr);
+ buffer = std::move(*bufferOrErr);
+ in.bufferStart = buffer->getBufferStart();
}
void Writer::writeSections() {
uint8_t *buf = buffer->getBufferStart();
+ std::vector<const OutputSection *> osecs;
for (const OutputSegment *seg : outputSegments)
- for (const OutputSection *osec : seg->getSections())
- osec->writeTo(buf + osec->fileOff);
+ append_range(osecs, seg->getSections());
+
+ parallelForEach(osecs.begin(), osecs.end(), [&](const OutputSection *osec) {
+ osec->writeTo(buf + osec->fileOff);
+ });
+}
+
+void Writer::applyOptimizationHints() {
+ if (config->arch() != AK_arm64 || config->ignoreOptimizationHints)
+ return;
+
+ uint8_t *buf = buffer->getBufferStart();
+ TimeTraceScope timeScope("Apply linker optimization hints");
+ parallelForEach(inputFiles, [buf](const InputFile *file) {
+ if (const auto *objFile = dyn_cast<ObjFile>(file))
+ target->applyOptimizationHints(buf, *objFile);
+ });
}
// In order to utilize multiple cores, we first split the buffer into chunks,
// values.
void Writer::writeUuid() {
TimeTraceScope timeScope("Computing UUID");
+
ArrayRef<uint8_t> data{buffer->getBufferStart(), buffer->getBufferEnd()};
unsigned chunkCount = parallel::strategy.compute_thread_count() * 10;
// Round-up integer division
size_t chunkSize = (data.size() + chunkCount - 1) / chunkCount;
std::vector<ArrayRef<uint8_t>> chunks = split(data, chunkSize);
- std::vector<uint64_t> hashes(chunks.size());
- parallelForEachN(0, chunks.size(),
- [&](size_t i) { hashes[i] = xxHash64(chunks[i]); });
+ // Leave one slot for filename
+ std::vector<uint64_t> hashes(chunks.size() + 1);
+ SmallVector<std::shared_future<void>> threadFutures;
+ threadFutures.reserve(chunks.size());
+ for (size_t i = 0; i < chunks.size(); ++i)
+ threadFutures.emplace_back(threadPool.async(
+ [&](size_t j) { hashes[j] = xxHash64(chunks[j]); }, i));
+ for (std::shared_future<void> &future : threadFutures)
+ future.wait();
+ // Append the output filename so that identical binaries with different names
+ // don't get the same UUID.
+ hashes[chunks.size()] = xxHash64(sys::path::filename(config->finalOutput));
uint64_t digest = xxHash64({reinterpret_cast<uint8_t *>(hashes.data()),
hashes.size() * sizeof(uint64_t)});
uuidCommand->writeUuid(digest);
}
+// This is step 5 of the algorithm described in the class comment of
+// ChainedFixupsSection.
+void Writer::buildFixupChains() {
+ if (!config->emitChainedFixups)
+ return;
+
+ const std::vector<Location> &loc = in.chainedFixups->getLocations();
+ if (loc.empty())
+ return;
+
+ TimeTraceScope timeScope("Build fixup chains");
+
+ const uint64_t pageSize = target->getPageSize();
+ constexpr uint32_t stride = 4; // for DYLD_CHAINED_PTR_64
+
+ for (size_t i = 0, count = loc.size(); i < count;) {
+ const OutputSegment *oseg = loc[i].isec->parent->parent;
+ uint8_t *buf = buffer->getBufferStart() + oseg->fileOff;
+ uint64_t pageIdx = loc[i].offset / pageSize;
+ ++i;
+
+ while (i < count && loc[i].isec->parent->parent == oseg &&
+ (loc[i].offset / pageSize) == pageIdx) {
+ uint64_t offset = loc[i].offset - loc[i - 1].offset;
+
+ auto fail = [&](Twine message) {
+ error(loc[i].isec->getSegName() + "," + loc[i].isec->getName() +
+ ", offset " +
+ Twine(loc[i].offset - loc[i].isec->parent->getSegmentOffset()) +
+ ": " + message);
+ };
+
+ if (offset < target->wordSize)
+ return fail("fixups overlap");
+ if (offset % stride != 0)
+ return fail(
+ "fixups are unaligned (offset " + Twine(offset) +
+ " is not a multiple of the stride). Re-link with -no_fixup_chains");
+
+ // The "next" field is in the same location for bind and rebase entries.
+ reinterpret_cast<dyld_chained_ptr_64_bind *>(buf + loc[i - 1].offset)
+ ->next = offset / stride;
+ ++i;
+ }
+ }
+}
+
void Writer::writeCodeSignature() {
- if (codeSignatureSection)
+ if (codeSignatureSection) {
+ TimeTraceScope timeScope("Write code signature");
codeSignatureSection->writeHashes(buffer->getBufferStart());
+ }
}
void Writer::writeOutputFile() {
TimeTraceScope timeScope("Write output file");
openFile();
+ reportPendingUndefinedSymbols();
if (errorCount())
return;
writeSections();
+ applyOptimizationHints();
+ buildFixupChains();
writeUuid();
writeCodeSignature();
if (auto e = buffer->commit())
- error("failed to write to the output file: " + toString(std::move(e)));
+ fatal("failed to write output '" + buffer->getPath() +
+ "': " + toString(std::move(e)));
}
template <class LP> void Writer::run() {
treatSpecialUndefineds();
- if (config->entry && !isa<Undefined>(config->entry))
- prepareBranchTarget(config->entry);
- scanRelocations();
- if (in.stubHelper->isNeeded())
- in.stubHelper->setup();
+ if (config->entry && needsBinding(config->entry))
+ in.stubs->addEntry(config->entry);
+
+ // Canonicalization of all pointers to InputSections should be handled by
+ // these two scan* methods. I.e. from this point onward, for all live
+ // InputSections, we should have `isec->canonical() == isec`.
scanSymbols();
+ if (in.objcStubs->isNeeded())
+ in.objcStubs->setUp();
+ scanRelocations();
+ if (in.initOffsets->isNeeded())
+ in.initOffsets->setUp();
+
+ // Do not proceed if there were undefined or duplicate symbols.
+ reportPendingUndefinedSymbols();
+ reportPendingDuplicateSymbols();
+ if (errorCount())
+ return;
+
+ if (in.stubHelper && in.stubHelper->isNeeded())
+ in.stubHelper->setUp();
+
+ if (in.objCImageInfo->isNeeded())
+ in.objCImageInfo->finalizeContents();
+
+ // At this point, we should know exactly which output sections are needed,
+ // courtesy of scanSymbols() and scanRelocations().
createOutputSections<LP>();
+
// After this point, we create no new segments; HOWEVER, we might
// yet create branch-range extension thunks for architectures whose
// hardware call instructions have limited range, e.g., ARM(64).
sortSegmentsAndSections();
createLoadCommands<LP>();
finalizeAddresses();
+ threadPool.async([&] {
+ if (LLVM_ENABLE_THREADS && config->timeTraceEnabled)
+ timeTraceProfilerInitialize(config->timeTraceGranularity, "writeMapFile");
+ writeMapFile();
+ if (LLVM_ENABLE_THREADS && config->timeTraceEnabled)
+ timeTraceProfilerFinishThread();
+ });
finalizeLinkEditSegment();
- writeMapFile();
writeOutputFile();
}
template <class LP> void macho::writeResult() { Writer().run<LP>(); }
+void macho::resetWriter() { LCDylib::resetInstanceCount(); }
+
void macho::createSyntheticSections() {
in.header = make<MachHeaderSection>();
- if (config->dedupLiterals) {
- in.cStringSection = make<DeduplicatedCStringSection>();
+ if (config->dedupStrings)
+ in.cStringSection =
+ make<DeduplicatedCStringSection>(section_names::cString);
+ else
+ in.cStringSection = make<CStringSection>(section_names::cString);
+ in.objcMethnameSection =
+ make<DeduplicatedCStringSection>(section_names::objcMethname);
+ in.wordLiteralSection = make<WordLiteralSection>();
+ if (config->emitChainedFixups) {
+ in.chainedFixups = make<ChainedFixupsSection>();
} else {
- in.cStringSection = make<CStringSection>();
+ in.rebase = make<RebaseSection>();
+ in.binding = make<BindingSection>();
+ in.weakBinding = make<WeakBindingSection>();
+ in.lazyBinding = make<LazyBindingSection>();
+ in.lazyPointers = make<LazyPointerSection>();
+ in.stubHelper = make<StubHelperSection>();
}
- in.wordLiteralSection =
- config->dedupLiterals ? make<WordLiteralSection>() : nullptr;
- in.rebase = make<RebaseSection>();
- in.binding = make<BindingSection>();
- in.weakBinding = make<WeakBindingSection>();
- in.lazyBinding = make<LazyBindingSection>();
in.exports = make<ExportSection>();
in.got = make<GotSection>();
in.tlvPointers = make<TlvPointerSection>();
- in.lazyPointers = make<LazyPointerSection>();
in.stubs = make<StubsSection>();
- in.stubHelper = make<StubHelperSection>();
+ in.objcStubs = make<ObjCStubsSection>();
in.unwindInfo = makeUnwindInfoSection();
+ in.objCImageInfo = make<ObjCImageInfoSection>();
+ in.initOffsets = make<InitOffsetsSection>();
// This section contains space for just a single word, and will be used by
// dyld to cache an address to the image loader it uses.
- uint8_t *arr = bAlloc.Allocate<uint8_t>(target->wordSize);
+ uint8_t *arr = bAlloc().Allocate<uint8_t>(target->wordSize);
memset(arr, 0, target->wordSize);
- in.imageLoaderCache = make<ConcatInputSection>(
- segment_names::data, section_names::data, /*file=*/nullptr,
+ in.imageLoaderCache = makeSyntheticInputSection(
+ segment_names::data, section_names::data, S_REGULAR,
ArrayRef<uint8_t>{arr, target->wordSize},
- /*align=*/target->wordSize, /*flags=*/S_REGULAR);
+ /*align=*/target->wordSize);
// References from dyld are not visible to us, so ensure this section is
// always treated as live.
in.imageLoaderCache->live = true;
#include <cstdint>
-namespace lld {
-namespace macho {
+namespace lld::macho {
class OutputSection;
class InputSection;
};
template <class LP> void writeResult();
+void resetWriter();
void createSyntheticSections();
extern OutputSection *firstTLVDataSection;
-} // namespace macho
-} // namespace lld
+} // namespace lld::macho
#endif
LINK_COMPONENTS
Option
Support
+ TargetParser
LINK_LIBS
lldCOFF
//===----------------------------------------------------------------------===//
#include "lld/Common/Driver.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
#include "lld/Common/Version.h"
#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/Path.h"
+#include <optional>
#if !defined(_MSC_VER) && !defined(__MINGW32__)
#include <unistd.h>
};
// Create prefix string literals used in Options.td
-#define PREFIX(NAME, VALUE) static const char *const NAME[] = VALUE;
+#define PREFIX(NAME, VALUE) \
+ static constexpr llvm::StringLiteral NAME##_init[] = VALUE; \
+ static constexpr llvm::ArrayRef<llvm::StringLiteral> NAME( \
+ NAME##_init, std::size(NAME##_init) - 1);
#include "Options.inc"
#undef PREFIX
// Create table mapping all options defined in Options.td
-static const opt::OptTable::Info infoTable[] = {
+static constexpr opt::OptTable::Info infoTable[] = {
#define OPTION(X1, X2, ID, KIND, GROUP, ALIAS, X7, X8, X9, X10, X11, X12) \
{X1, X2, X10, X11, OPT_##ID, opt::Option::KIND##Class, \
X9, X8, OPT_##GROUP, OPT_##ALIAS, X7, X12},
};
namespace {
-class MinGWOptTable : public opt::OptTable {
+class MinGWOptTable : public opt::GenericOptTable {
public:
- MinGWOptTable() : OptTable(infoTable, false) {}
+ MinGWOptTable() : opt::GenericOptTable(infoTable, false) {}
opt::InputArgList parse(ArrayRef<const char *> argv);
};
} // namespace
unsigned missingCount;
SmallVector<const char *, 256> vec(argv.data(), argv.data() + argv.size());
- cl::ExpandResponseFiles(saver, getQuotingStyle(), vec);
+ cl::ExpandResponseFiles(saver(), getQuotingStyle(), vec);
opt::InputArgList args = this->ParseArgs(vec, missingIndex, missingCount);
if (missingCount)
}
// Find a file by concatenating given paths.
-static Optional<std::string> findFile(StringRef path1, const Twine &path2) {
+static std::optional<std::string> findFile(StringRef path1,
+ const Twine &path2) {
SmallString<128> s;
sys::path::append(s, path1, path2);
if (sys::fs::exists(s))
return std::string(s);
- return None;
+ return std::nullopt;
}
// This is for -lfoo. We'll look for libfoo.dll.a or libfoo.a from search paths.
searchLibrary(StringRef name, ArrayRef<StringRef> searchPaths, bool bStatic) {
if (name.startswith(":")) {
for (StringRef dir : searchPaths)
- if (Optional<std::string> s = findFile(dir, name.substr(1)))
+ if (std::optional<std::string> s = findFile(dir, name.substr(1)))
return *s;
error("unable to find library -l" + name);
return "";
for (StringRef dir : searchPaths) {
if (!bStatic) {
- if (Optional<std::string> s = findFile(dir, "lib" + name + ".dll.a"))
+ if (std::optional<std::string> s = findFile(dir, "lib" + name + ".dll.a"))
return *s;
- if (Optional<std::string> s = findFile(dir, name + ".dll.a"))
+ if (std::optional<std::string> s = findFile(dir, name + ".dll.a"))
return *s;
}
- if (Optional<std::string> s = findFile(dir, "lib" + name + ".a"))
+ if (std::optional<std::string> s = findFile(dir, "lib" + name + ".a"))
+ return *s;
+ if (std::optional<std::string> s = findFile(dir, name + ".lib"))
return *s;
if (!bStatic) {
- if (Optional<std::string> s = findFile(dir, name + ".lib"))
- return *s;
- if (Optional<std::string> s = findFile(dir, "lib" + name + ".dll"))
+ if (std::optional<std::string> s = findFile(dir, "lib" + name + ".dll"))
return *s;
- if (Optional<std::string> s = findFile(dir, name + ".dll"))
+ if (std::optional<std::string> s = findFile(dir, name + ".dll"))
return *s;
}
}
// Convert Unix-ish command line arguments to Windows-ish ones and
// then call coff::link.
-bool mingw::link(ArrayRef<const char *> argsArr, bool canExitEarly,
- raw_ostream &stdoutOS, raw_ostream &stderrOS) {
- lld::stdoutOS = &stdoutOS;
- lld::stderrOS = &stderrOS;
-
- stderrOS.enable_colors(stderrOS.has_colors());
+bool mingw::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly,
+ bool disableOutput) {
+ auto *ctx = new CommonLinkerContext;
+ ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
MinGWOptTable parser;
opt::InputArgList args = parser.parse(argsArr.slice(1));
add("-filealign:" + StringRef(a->getValue()));
if (auto *a = args.getLastArg(OPT_section_alignment))
add("-align:" + StringRef(a->getValue()));
+ if (auto *a = args.getLastArg(OPT_heap))
+ add("-heap:" + StringRef(a->getValue()));
if (auto *a = args.getLastArg(OPT_o))
add("-out:" + StringRef(a->getValue()));
if (args.hasFlag(OPT_disable_tsaware, OPT_tsaware, false))
add("-tsaware:no");
+ if (args.hasFlag(OPT_disable_reloc_section, OPT_enable_reloc_section, false))
+ add("-fixed");
+
if (args.hasFlag(OPT_no_insert_timestamp, OPT_insert_timestamp, false))
add("-timestamp:0");
error("unknown parameter: -m" + s);
}
+ if (args.hasFlag(OPT_guard_cf, OPT_no_guard_cf, false)) {
+ if (args.hasFlag(OPT_guard_longjmp, OPT_no_guard_longjmp, true))
+ add("-guard:cf,longjmp");
+ else
+ add("-guard:cf,nolongjmp");
+ } else if (args.hasFlag(OPT_guard_longjmp, OPT_no_guard_longjmp, false)) {
+ auto *a = args.getLastArg(OPT_guard_longjmp);
+ warn("parameter " + a->getSpelling() +
+ " only takes effect when used with --guard-cf");
+ }
+
+ if (auto *a = args.getLastArg(OPT_error_limit)) {
+ int n;
+ StringRef s = a->getValue();
+ if (s.getAsInteger(10, n))
+ error(a->getSpelling() + ": number expected, but got " + s);
+ else
+ add("-errorlimit:" + s);
+ }
+
for (auto *a : args.filtered(OPT_mllvm))
add("-mllvm:" + StringRef(a->getValue()));
add("-delayload:" + StringRef(a->getValue()));
for (auto *a : args.filtered(OPT_wrap))
add("-wrap:" + StringRef(a->getValue()));
+ for (auto *a : args.filtered(OPT_exclude_symbols))
+ add("-exclude-symbols:" + StringRef(a->getValue()));
std::vector<StringRef> searchPaths;
for (auto *a : args.filtered(OPT_L)) {
// Pass the actual binary name, to make error messages be printed with
// the right prefix.
vec[0] = argsArr[0];
- return coff::link(vec, canExitEarly, stdoutOS, stderrOS);
+
+ // The context will be re-created in the COFF driver.
+ lld::CommonLinkerContext::destroy();
+
+ return coff::link(vec, stdoutOS, stderrOS, exitEarly, disableOutput);
}
def disable_ # NAME: Flag<["--", "-"], "disable-" # name>, HelpText<help2>;
}
+multiclass B_enable_disable<string name, string help1, string help2> {
+ def enable_ # NAME: Flag<["--", "-"], "enable-" # name>, HelpText<help1>;
+ def disable_ # NAME: Flag<["--", "-"], "disable-" # name>, HelpText<help2>;
+}
+
def L: JoinedOrSeparate<["-"], "L">, MetaVarName<"<dir>">,
HelpText<"Add a directory to the library search path">;
defm allow_multiple_definition: B<"allow-multiple-definition",
defm entry: Eq<"entry", "Name of entry point symbol">, MetaVarName<"<entry>">;
def exclude_all_symbols: F<"exclude-all-symbols">,
HelpText<"Don't automatically export any symbols">;
+defm exclude_symbols: Eq<"exclude-symbols",
+ "Exclude symbols from automatic export">, MetaVarName<"<symbol[,symbol,...]>">;
def export_all_symbols: F<"export-all-symbols">,
HelpText<"Export all symbols even if a def file or dllexport attributes are used">;
defm fatal_warnings: B<"fatal-warnings",
defm gc_sections: B<"gc-sections",
"Remove unused sections",
"Don't remove unused sections">;
+defm heap: Eq<"heap", "Set size of the initial heap">;
def help: F<"help">, HelpText<"Print option help">;
defm high_entropy_va: B_disable<"high-entropy-va",
"Set the 'high entropy VA' flag", "Don't set the 'high entropy VA' flag">;
HelpText<"Path to file to write output">;
defm out_implib: Eq<"out-implib", "Import library name">;
defm output_def: Eq<"output-def", "Output def file">;
+defm reloc_section: B_enable_disable<"reloc-section",
+ "Enable base relocations", "Disable base relocations">;
defm section_alignment: Eq<"section-alignment", "Set section alignment">;
def shared: F<"shared">, HelpText<"Build a shared object">;
defm subs: Eq<"subsystem", "Specify subsystem">;
defm thinlto_cache_dir: EqLong<"thinlto-cache-dir",
"Path to ThinLTO cached object file directory">;
defm Xlink : Eq<"Xlink", "Pass <arg> to the COFF linker">, MetaVarName<"<arg>">;
+defm guard_cf : B<"guard-cf", "Enable Control Flow Guard" ,
+ "Do not enable Control Flow Guard (default)">;
+defm guard_longjmp : B<"guard-longjmp",
+ "Enable Control Flow Guard long jump hardening (default for --guard-cf)" ,
+ "Do not enable Control Flow Guard long jump hardening">;
+defm error_limit:
+ EqLong<"error-limit", "Maximum number of errors to emit before stopping (0 = no limit)">;
// Alias
def alias_Bdynamic_call_shared: Flag<["-"], "call_shared">, Alias<Bdynamic>;
// Ignored options
def: Joined<["-"], "O">;
+def: F<"as-needed">;
def: F<"build-id">;
def: F<"disable-auto-image-base">;
def: F<"enable-auto-image-base">;
+include(GNUInstallDirs)
include(LLVMDistributionSupport)
macro(add_lld_library name)
${export_to_lldtargets}
LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX}
ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX}
- RUNTIME DESTINATION bin)
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
if (${ARG_SHARED} AND NOT CMAKE_CONFIGURATION_TYPES)
add_llvm_install_targets(install-${name}
get_target_export_arg(${name} LLD export_to_lldtargets)
install(TARGETS ${name}
${export_to_lldtargets}
- RUNTIME DESTINATION bin
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
COMPONENT ${name})
if(NOT CMAKE_CONFIGURATION_TYPES)
endmacro()
macro(add_lld_symlink name dest)
- add_llvm_tool_symlink(${name} ${dest} ALWAYS_GENERATE)
+ llvm_add_tool_symlink(LLD ${name} ${dest} ALWAYS_GENERATE)
# Always generate install targets
- llvm_install_symlink(${name} ${dest} ALWAYS_GENERATE)
+ llvm_install_symlink(LLD ${name} ${dest} ALWAYS_GENERATE)
endmacro()
+include(GNUInstallPackageDir)
+include(ExtendPath)
+include(FindPrefixFromConfig)
+
# Generate a list of CMake library targets so that other CMake projects can
# link against them. LLVM calls its version of this file LLVMExports.cmake, but
# the usual CMake convention seems to be ${Project}Targets.cmake.
-set(LLD_INSTALL_PACKAGE_DIR lib${LLVM_LIBDIR_SUFFIX}/cmake/lld)
-set(lld_cmake_builddir "${CMAKE_BINARY_DIR}/${LLD_INSTALL_PACKAGE_DIR}")
+set(LLD_INSTALL_PACKAGE_DIR "${CMAKE_INSTALL_PACKAGEDIR}/lld" CACHE STRING
+ "Path for CMake subdirectory for LLD (defaults to '${CMAKE_INSTALL_PACKAGEDIR}/lld')")
+# CMAKE_INSTALL_PACKAGEDIR might be absolute, so don't reuse below.
+set(lld_cmake_builddir "${CMAKE_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX}/cmake/lld")
# Keep this in sync with llvm/cmake/CMakeLists.txt!
-set(LLVM_INSTALL_PACKAGE_DIR lib${LLVM_LIBDIR_SUFFIX}/cmake/llvm)
-set(llvm_cmake_builddir "${LLVM_BINARY_DIR}/${LLVM_INSTALL_PACKAGE_DIR}")
+set(LLVM_INSTALL_PACKAGE_DIR "${CMAKE_INSTALL_PACKAGEDIR}/llvm" CACHE STRING
+ "Path for CMake subdirectory for LLVM (defaults to '${CMAKE_INSTALL_PACKAGEDIR}/llvm')")
+# CMAKE_INSTALL_PACKAGEDIR might be absolute, so don't reuse below.
+string(REPLACE "${CMAKE_CFG_INTDIR}" "." llvm_cmake_builddir "${LLVM_LIBRARY_DIR}")
+set(llvm_cmake_builddir "${llvm_cmake_builddir}/cmake/llvm")
get_property(LLD_EXPORTS GLOBAL PROPERTY LLD_EXPORTS)
export(TARGETS ${LLD_EXPORTS} FILE ${lld_cmake_builddir}/LLDTargets.cmake)
${CMAKE_CURRENT_SOURCE_DIR}/LLDConfig.cmake.in
${lld_cmake_builddir}/LLDConfig.cmake
@ONLY)
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/LLDConfigVersion.cmake.in
+ ${lld_cmake_builddir}/LLDConfigVersion.cmake
+ @ONLY)
set(LLD_CONFIG_CMAKE_DIR)
set(LLD_CONFIG_LLVM_CMAKE_DIR)
# Generate LLDConfig.cmake for the install tree.
-set(LLD_CONFIG_CODE "
-# Compute the installation prefix from this LLVMConfig.cmake file location.
-get_filename_component(LLD_INSTALL_PREFIX \"\${CMAKE_CURRENT_LIST_FILE}\" PATH)")
-# Construct the proper number of get_filename_component(... PATH)
-# calls to compute the installation prefix.
-string(REGEX REPLACE "/" ";" _count "${LLD_INSTALL_PACKAGE_DIR}")
-foreach(p ${_count})
- set(LLD_CONFIG_CODE "${LLD_CONFIG_CODE}
-get_filename_component(LLD_INSTALL_PREFIX \"\${LLD_INSTALL_PREFIX}\" PATH)")
-endforeach(p)
-set(LLD_CONFIG_CMAKE_DIR "\${LLD_INSTALL_PREFIX}/${LLD_INSTALL_PACKAGE_DIR}")
-set(LLD_CONFIG_LLVM_CMAKE_DIR "\${LLD_INSTALL_PREFIX}/${LLVM_INSTALL_PACKAGE_DIR}")
+find_prefix_from_config(LLD_CONFIG_CODE LLD_INSTALL_PREFIX "${LLD_INSTALL_PACKAGE_DIR}")
+extend_path(LLD_CONFIG_CMAKE_DIR "\${LLD_INSTALL_PREFIX}" "${LLD_INSTALL_PACKAGE_DIR}")
+extend_path(LLD_CONFIG_LLVM_CMAKE_DIR "\${LLD_INSTALL_PREFIX}" "${LLVM_INSTALL_PACKAGE_DIR}")
get_config_exports_includes(LLD LLD_CONFIG_INCLUDE_EXPORTS)
-set(LLD_CONFIG_INCLUDE_DIRS "\${LLD_INSTALL_PREFIX}/include")
+extend_path(LLD_CONFIG_INCLUDE_DIRS "\${LLD_INSTALL_PREFIX}" "${CMAKE_INSTALL_INCLUDEDIR}")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/LLDConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/LLDConfig.cmake
@ONLY)
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/LLDConfigVersion.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/LLDConfigVersion.cmake
+ @ONLY)
set(LLD_CONFIG_CODE)
set(LLD_CONFIG_CMAKE_DIR)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/LLDConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/LLDConfigVersion.cmake
DESTINATION ${LLD_INSTALL_PACKAGE_DIR}
COMPONENT lld-cmake-exports)
@LLD_CONFIG_CODE@
-find_package(LLVM REQUIRED CONFIG
+set(LLVM_VERSION @LLVM_VERSION_MAJOR@.@LLVM_VERSION_MINOR@.@LLVM_VERSION_PATCH@)
+find_package(LLVM ${LLVM_VERSION} EXACT REQUIRED CONFIG
HINTS "@LLD_CONFIG_LLVM_CMAKE_DIR@")
set(LLD_EXPORTED_TARGETS "@LLD_EXPORTS@")
--- /dev/null
+set(PACKAGE_VERSION "@PACKAGE_VERSION@")
+
+# LLVM is API-compatible only with matching major.minor versions
+# and patch versions not less than that requested.
+if("@LLVM_VERSION_MAJOR@.@LLVM_VERSION_MINOR@" VERSION_EQUAL
+ "${PACKAGE_FIND_VERSION_MAJOR}.${PACKAGE_FIND_VERSION_MINOR}"
+ AND NOT "@LLVM_VERSION_PATCH@" VERSION_LESS "${PACKAGE_FIND_VERSION_PATCH}")
+ set(PACKAGE_VERSION_COMPATIBLE 1)
+ if("@LLVM_VERSION_PATCH@" VERSION_EQUAL
+ "${PACKAGE_FIND_VERSION_PATCH}")
+ set(PACKAGE_VERSION_EXACT 1)
+ endif()
+endif()
command is followed by ``INSERT``, LLD applies built-in rules which are similar
to GNU ld's internal linker scripts.
-- Align the first section in a ``PT_LOAD`` segment according to ``-z noseparate-code``,
- ``-z separate-code``, or ``-z separate-loadable-segments``
-- Define ``__bss_start``, ``end``, ``_end``, ``etext``, ``_etext``, ``edata``, ``_edata``
-- Sort ``.ctors.*``/``.dtors.*``/``.init_array.*``/``.fini_array.*`` and PowerPC64 specific ``.toc``
+- Align the first section in a ``PT_LOAD`` segment according to
+ ``-z noseparate-code``, ``-z separate-code``, or
+ ``-z separate-loadable-segments``
+- Define ``__bss_start``, ``end``, ``_end``, ``etext``, ``_etext``, ``edata``,
+ ``_edata``
+- Sort ``.ctors.*``/``.dtors.*``/``.init_array.*``/``.fini_array.*`` and
+ PowerPC64 specific ``.toc``
- Place input ``.text.*`` into output ``.text``, and handle certain variants
- (``.text.hot.``, ``.text.unknown.``, ``.text.unlikely.``, etc) in the precense of
- ``-z keep-text-section-prefix``.
+ (``.text.hot.``, ``.text.unknown.``, ``.text.unlikely.``, etc) in the
+ presence of ``-z keep-text-section-prefix``.
Output section description
~~~~~~~~~~~~~~~~~~~~~~~~~~
GNU ld from Binutils 2.35 onwards will reduce sh_addralign so that
sh_addr=0 (modulo sh_addralign).
+Output section type
+-------------------
+
+When an *OutputSection* *S* has ``(type)``, LLD will set ``sh_type`` or
+``sh_flags`` of *S*. ``type`` is one of:
+
+- ``NOLOAD``: set ``sh_type`` to ``SHT_NOBITS``.
+- ``COPY``, ``INFO``, ``OVERLAY``: clear the ``SHF_ALLOC`` bit in ``sh_flags``.
+- ``TYPE=<value>``: set ``sh_type`` to the specified value. ``<value>`` must be
+ an integer or one of ``SHT_PROGBITS, SHT_NOTE, SHT_NOBITS, SHT_INIT_ARRAY,
+ SHT_FINI_ARRAY, SHT_PREINIT_ARRAY``.
+
+When ``sh_type`` is specified, it is an error if an input section in *S* has a
+different type.
+
Output section alignment
------------------------
--- /dev/null
+-z start-stop-gc
+================
+
+If your ``-Wl,--gc-sections`` build fail with a linker error like this:
+
+ error: undefined symbol: __start_meta
+ >>> referenced by {{.*}}
+ >>> the encapsulation symbol needs to be retained under --gc-sections properly; consider -z nostart-stop-gc (see https://lld.llvm.org/start-stop-gc)
+
+it is likely your C identifier name sections are not properly annotated to
+suffice under ``--gc-sections``.
+
+``__start_meta`` and ``__stop_meta`` are sometimed called encapsulation
+symbols. In October 2015, GNU ld switched behavior and made a ``__start_meta``
+reference from a live section retain all ``meta`` input sections. This
+conservative behavior works for existing code which does not take GC into fair
+consideration, but unnecessarily increases sizes for modern metadata section
+usage which desires precise GC.
+
+GNU ld 2.37 added ``-z start-stop-gc`` to restore the traditional behavior
+ld.lld 13.0.0 defaults to ``-z start-stop-gc`` and supports ``-z nostart-stop-gc``
+to switch to the conservative behavior.
+
+The Apple ld64 linker has a similar ``section$start`` feature and always
+allowed GC (like ``-z start-stop-gc``).
+
+Annotate C identifier name sections
+-----------------------------------
+
+A C identifier name section (``meta``) sometimes depends on another section.
+Let that section reference ``meta`` via a relocation.
+
+.. code-block:: c
+
+ asm(".pushsection .init_array,\"aw\",@init_array\n" \
+ ".reloc ., R_AARCH64_NONE, meta\n" \
+ ".popsection\n")
+
+If a relocation is inconvenient, consider using ``__attribute__((retain))``
+(GCC 11 with modern binutils, Clang 13).
+
+.. code-block:: c
+
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wattributes"
+ __attribute__((retain,used,section("meta")))
+ static const char dummy[0];
+ #pragma GCC diagnostic pop
+
+GCC before 11 and Clang before 13 do not recognize ``__attribute__((retain))``,
+so ``-Wattributes`` may need to be ignored. On ELF targets,
+``__attribute__((used))`` prevents compiler discarding, but does not affect
+linker ``--gc-sections``.
+
+In a macro, you may use:
+
+.. code-block:: c
+
+ _Pragma("GCC diagnostic push")
+ _Pragma("GCC diagnostic ignored \"-Wattributes\"")
+ ...
+ _Pragma("GCC diagnostic pop")
+
+If you use the ``SECTIONS`` command in a linker script, use
+`the ``KEEP`` keyword <https://sourceware.org/binutils/docs/ld/Input-Section-Keep.html>`_, e.g.
+``meta : { KEEP(*(meta)) }``
--- /dev/null
+Mach-O LLD Port
+===============
+
+LLD is a linker from the LLVM project that is a drop-in replacement
+for system linkers and runs much faster than them. It also provides
+features that are useful for toolchain developers. This document
+will describe the Mach-O port.
+
+Features
+--------
+
+- LLD is a drop-in replacement for Apple's Mach-O linker, ld64, that accepts the
+ same command line arguments.
+
+- LLD is very fast. When you link a large program on a multicore
+ machine, you can expect that LLD runs more than twice as fast as the ld64
+ linker.
+
+Download
+--------
+
+LLD is available as a pre-built binary by going to the `latest release <https://github.com/llvm/llvm-project/releases>`_,
+downloading the appropriate bundle (``clang+llvm-<version>-<your architecture>-<your platform>.tar.xz``),
+decompressing it, and locating the binary at ``bin/ld64.lld``. Note
+that if ``ld64.lld`` is moved out of ``bin``, it must still be accompanied
+by its sibling file ``lld``, as ``ld64.lld`` is technically a symlink to ``lld``.
+
+Build
+-----
+
+The easiest way to build LLD is to
+check out the entire LLVM projects/sub-projects from a git mirror and
+build that tree. You need ``cmake`` and of course a C++ compiler.
+
+.. code-block:: console
+
+ $ git clone https://github.com/llvm/llvm-project llvm-project
+ $ mkdir build
+ $ cd build
+ $ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS='lld' ../llvm-project/llvm
+ $ ninja check-lld-macho
+
+Then you can find output binary at ``build/bin/ld64.lld``. Note
+that if ``ld64.lld`` is moved out of ``bin``, it must still be accompanied
+by its sibling file ``lld``, as ``ld64.lld`` is technically a symlink to ``lld``.
+
+Using LLD
+---------
+
+LLD can be used by adding ``-fuse-ld=/path/to/ld64.lld`` to the linker flags.
+For Xcode, this can be done by adding it to "Other linker flags" in the build
+settings. For Bazel, this can be done with ``--linkopt`` or with
+`rules_apple_linker <https://github.com/keith/rules_apple_linker>`_.
+
+.. seealso::
+
+ :doc:`ld64-vs-lld` has more info on the differences between the two linkers.
+
+.. toctree::
+ :hidden:
+
+ ld64-vs-lld
--- /dev/null
+=================
+ld64 vs LLD-MachO
+=================
+
+This doc lists all significant deliberate differences in behavior between ld64
+and LLD-MachO.
+
+Dead Stripping Duplicate Symbols
+********************************
+ld64 strips dead code before reporting duplicate symbols. By default, LLD does
+the opposite. ld64's behavior hides ODR violations, so we have chosen not
+to follow it. But, to make adoption easy, LLD can mimic this behavior via
+the ``--dead-strip-duplicates`` flag. Usage of this flag is discouraged, and
+this behavior should be fixed in the source. However, for sources that are not
+within the user's control, this will mitigate users for adoption.
+
+``-no_deduplicate`` Flag
+************************
+- ld64: This turns off ICF (deduplication pass) in the linker.
+- LLD: This turns off ICF and string merging in the linker.
+
+String Alignment
+****************
+LLD is `slightly less conservative about aligning cstrings
+<https://reviews.llvm.org/D121342>`_, allowing it to pack them more compactly.
+This should not result in any meaningful semantic difference.
+
+ObjC Symbols Treatment
+**********************
+There are differences in how LLD and ld64 handle ObjC symbols loaded from
+archives.
+
+- ld64:
+ 1. Duplicate ObjC symbols from the same archives will not raise an error.
+ ld64 will pick the first one.
+ 2. Duplicate ObjC symbols from different archives will raise a "duplicate
+ symbol" error.
+- LLD: Duplicate symbols, regardless of which archives they are from, will
+ raise errors.
+
+Aliases
+=======
+ld64 treats all aliases as strong extern definitions. Having two aliases of the
+same name, or an alias plus a regular extern symbol of the same name, both
+result in duplicate symbol errors. LLD does not check for duplicate aliases;
+instead we perform alias resolution first, and only then do we check for
+duplicate symbols. In particular, we will not report a duplicate symbol error if
+the aliased symbols turn out to be weak definitions, but ld64 will.
+
+``ZERO_AR_DATE`` enabled by default
+***********************************
+ld64 has a special mode where it sets some stabs modification times to 0 for
+hermetic builds, enabled by setting any value for the ``ZERO_AR_DATE``
+environment variable. LLD flips this default to perfer hermetic builds, but
+allows disabling this behavior by setting ``ZERO_AR_DATE=0``. Any other value
+of ``ZERO_AR_DATE`` will be ignored.
-========================
-lld 13.0.0 Release Notes
-========================
+===========================
+lld |release| Release Notes
+===========================
.. contents::
:local:
-.. warning::
- These are in-progress notes for the upcoming LLVM 13.0.0 release.
- Release notes for previous releases can be found on
- `the Download Page <https://releases.llvm.org/download.html>`_.
+.. only:: PreRelease
+
+ .. warning::
+ These are in-progress notes for the upcoming LLVM |release| release.
+ Release notes for previous releases can be found on
+ `the Download Page <https://releases.llvm.org/download.html>`_.
Introduction
============
-This document contains the release notes for the lld linker, release 13.0.0.
+This document contains the release notes for the lld linker, release |release|.
Here we describe the status of lld, including major improvements
from the previous release. All lld releases may be downloaded
from the `LLVM releases web site <https://llvm.org/releases/>`_.
ELF Improvements
----------------
-* ``-z start-stop-gc`` is now supported and becomes the default.
- (`D96914 <https://reviews.llvm.org/D96914>`_)
- (`rG6d2d3bd0 <https://reviews.llvm.org/rG6d2d3bd0a61f5fc7fd9f61f48bc30e9ca77cc619>`_)
-* ``--shuffle-sections=<seed>`` has been changed to ``--shuffle-sections=<section-glob>=<seed>``.
- If seed is -1, the matched input sections are reversed.
- (`D98445 <https://reviews.llvm.org/D98445>`_)
- (`D98679 <https://reviews.llvm.org/D98679>`_)
-* ``-Bsymbolic -Bsymbolic-functions`` has been changed to behave the same as ``-Bsymbolic-functions``. This matches GNU ld.
- (`D102461 <https://reviews.llvm.org/D102461>`_)
-* ``-Bno-symbolic`` has been added.
- (`D102461 <https://reviews.llvm.org/D102461>`_)
-* A new linker script command ``OVERWRITE_SECTIONS`` has been added.
- (`D103303 <https://reviews.llvm.org/D103303>`_)
-* ``-Bsymbolic-non-weak-functions`` has been added as a ``STB_GLOBAL`` subset of ``-Bsymbolic-functions``.
- (`D102570 <https://reviews.llvm.org/D102570>`_)
-* ``--no-allow-shlib-undefined`` has been improved to catch more cases.
- (`D101996 <https://reviews.llvm.org/D101996>`_)
-* ``__rela_iplt_start`` is no longer defined for -pie/-shared.
- This makes GCC/Clang ``-static-pie`` built executables work.
- (`rG8cb78e99 <https://reviews.llvm.org/rf8cb78e99aae9aa3f89f7bfe667db2c5b767f21f>`_)
-* IRELATIVE/TLSDESC relocations now support ``-z rel``.
- (`D100544 <https://reviews.llvm.org/D100544>`_)
-* Section groups with a zero flag are now supported.
- This is used by ``comdat nodeduplicate`` in LLVM IR.
- (`D96636 <https://reviews.llvm.org/D96636>`_)
- (`D106228 <https://reviews.llvm.org/D106228>`_)
-* Defined symbols are now resolved before undefined symbols to stabilize the bheavior of archive member extraction.
- (`D95985 <https://reviews.llvm.org/D95985>`_)
-* ``STB_WEAK`` symbols are now preferred over COMMON symbols as a fix to a ``--fortran-common`` regression.
- (`D105945 <https://reviews.llvm.org/D105945>`_)
-* Absolute relocations referencing undef weak now produce dynamic relocations for -pie, matching GOT-generating relocations.
- (`D105164 <https://reviews.llvm.org/D105164>`_)
-* Exported symbols are now communicated to the LTO library so as to make LTO
- based whole program devirtualization (``-flto=thin -fwhole-program-vtables``)
- work with shared objects.
- (`D91583 <https://reviews.llvm.org/D91583>`_)
-* Whole program devirtualization now respects ``local:`` version nodes in a version script.
- (`D98220 <https://reviews.llvm.org/D98220>`_)
- (`D98686 <https://reviews.llvm.org/D98686>`_)
-* ``local:`` version nodes in a version script now apply to non-default version symbols.
- (`D107234 <https://reviews.llvm.org/D107234>`_)
-* If an object file defines both ``foo`` and ``foo@v1``, now only ``foo@v1`` will be in the output.
- (`D107235 <https://reviews.llvm.org/D107235>`_)
-* Copy relocations on non-default version symbols are now supported.
- (`D107535 <https://reviews.llvm.org/D107535>`_)
-
-Linker script changes:
-
-* ``.``, ``$``, and double quotes can now be used in symbol names in expressions.
- (`D98306 <https://reviews.llvm.org/D98306>`_)
- (`rGe7a7ad13 <https://reviews.llvm.org/rGe7a7ad134fe182aad190cb3ebc441164470e92f5>`_)
-* Fixed value of ``.`` in the output section description of ``.tbss``.
- (`D107288 <https://reviews.llvm.org/D107288>`_)
-* ``NOLOAD`` sections can now be placed in a ``PT_LOAD`` program header.
- (`D103815 <https://reviews.llvm.org/D103815>`_)
-* ``OUTPUT_FORMAT(default, big, little)`` now consults ``-EL`` and ``-EB``.
- (`D96214 <https://reviews.llvm.org/D96214>`_)
-* The ``OVERWRITE_SECTIONS`` command has been added.
- (`D103303 <https://reviews.llvm.org/D103303>`_)
-* The section order within an ``INSERT AFTER`` command is now preserved.
- (`D105158 <https://reviews.llvm.org/D105158>`_)
-
-Architecture specific changes:
-
-* aarch64_be is now supported.
- (`D96188 <https://reviews.llvm.org/D96188>`_)
-* The AMDGPU port now supports ``--amdhsa-code-object-version=4`` object files;
- (`D95811 <https://reviews.llvm.org/D95811>`_)
-* The ARM port now accounts for PC biases in range extension thunk creation.
- (`D97550 <https://reviews.llvm.org/D97550>`_)
-* The AVR port now computes ``e_flags``.
- (`D99754 <https://reviews.llvm.org/D99754>`_)
-* The Mips port now omits unneeded dynamic relocations for PIE non-preemptible TLS.
- (`D101382 <https://reviews.llvm.org/D101382>`_)
-* The PowerPC port now supports ``--power10-stubs=no`` to omit Power10 instructions from call stubs.
- (`D94625 <https://reviews.llvm.org/D94625>`_)
-* Fixed a thunk creation bug in the PowerPC port when TOC/NOTOC calls are mixed.
- (`D101837 <https://reviews.llvm.org/D101837>`_)
-* The RISC-V port now resolves undefined weak relocations to the current location if not using PLT.
- (`D103001 <https://reviews.llvm.org/D103001>`_)
-* ``R_386_GOTOFF`` relocations from .debug_info are now allowed to be compatible with GCC.
- (`D95994 <https://reviews.llvm.org/D95994>`_)
-* ``gotEntrySize`` has been added to improve support for the ILP32 ABI of x86-64.
- (`D102569 <https://reviews.llvm.org/D102569>`_)
+* Link speed improved greatly compared with lld 15.0. Notably input section
+ initialization and relocation scanning are now parallel.
+ (`D130810 <https://reviews.llvm.org/D130810>`_)
+ (`D133003 <https://reviews.llvm.org/D133003>`_)
+* ``ELFCOMPRESS_ZSTD`` compressed input sections are now supported.
+ (`D129406 <https://reviews.llvm.org/D129406>`_)
+* ``--compress-debug-sections=zstd`` is now available to compress debug
+ sections with zstd (``ELFCOMPRESS_ZSTD``).
+ (`D133548 <https://reviews.llvm.org/D133548>`_)
+* ``--no-warnings``/``-w`` is now available to suppress warnings.
+ (`D136569 <https://reviews.llvm.org/D136569>`_)
+* ``DT_RISCV_VARIANT_CC`` is now produced if at least one ``R_RISCV_JUMP_SLOT``
+ relocation references a symbol with the ``STO_RISCV_VARIANT_CC`` bit.
+ (`D107951 <https://reviews.llvm.org/D107951>`_)
+* ``DT_STATIC_TLS`` is now set for AArch64/PPC32/PPC64 initial-exec TLS models
+ when producing a shared object.
+* ``--no-undefined-version`` is now the default; symbols named in version
+ scripts that have no matching symbol in the output will be reported. Use
+ ``--undefined-version`` to revert to the old behavior.
+ (`D135402 <https://reviews.llvm.org/D135402>`_)
+* ``-V`` is now an alias for ``-v`` to support ``gcc -fuse-ld=lld -v`` on many targets.
+* ``-r`` no longer defines ``__global_pointer$`` or ``_TLS_MODULE_BASE_``.
+* A corner case of mixed GCC and Clang object files (``STB_WEAK`` and
+ ``STB_GNU_UNIQUE`` in different COMDATs) is now supported.
+ (`D136381 <https://reviews.llvm.org/D136381>`_)
+* The output ``SHT_RISCV_ATTRIBUTES`` section now merges all input components
+ instead of picking the first input component.
+ (`D138550 <https://reviews.llvm.org/D138550>`_)
+* For x86-32, ``-fno-plt`` GD/LD TLS models ``call *___tls_get_addr@GOT(%reg)``
+ are now supported. Previous output might have runtime crash.
+* Armv4(T) thunks are now supported.
+ (`D139888 <https://reviews.llvm.org/D139888>`_)
+ (`D141272 <https://reviews.llvm.org/D141272>`_)
Breaking changes
----------------
-* ``--shuffle-sections=<seed>`` has been changed to ``--shuffle-sections=<section-glob>=<seed>``.
- Specify ``*`` as ``<section-glob>`` to get the previous behavior.
-
COFF Improvements
-----------------
-* Avoid thread exhaustion when running on 32 bit Windows.
- (`D105506 <https://reviews.llvm.org/D105506>`_)
-
-* Improve terminating the process on Windows while a thread pool might be
- running. (`D102944 <https://reviews.llvm.org/D102944>`_)
+* The linker command line entry in ``S_ENVBLOCK`` of the PDB is now stripped
+ from input files, to align with MSVC behavior.
+ (`D137723 <https://reviews.llvm.org/D137723>`_)
+* Switched from SHA1 to BLAKE3 for PDB type hashing / ``-gcodeview-ghash``
+ (`D137101 <https://reviews.llvm.org/D137101>`_)
+* Improvements to the PCH.OBJ files handling. Now LLD behaves the same as MSVC
+ link.exe when merging PCH.OBJ files that don't have the same signature.
+ (`D136762 <https://reviews.llvm.org/D136762>`_)
+* Changed the OrdinalBase for DLLs from 0 to 1, matching the output from
+ both MS link.exe and GNU ld. (`D134140 <https://reviews.llvm.org/D134140>`_)
MinGW Improvements
------------------
-* Support for linking directly against a DLL without using an import library
- has been added. (`D104530 <https://reviews.llvm.org/D104530>`_ and
- `D104531 <https://reviews.llvm.org/D104531>`_)
-
-* Fix linking with ``--export-all-symbols`` in combination with
- ``-function-sections``. (`D101522 <https://reviews.llvm.org/D101522>`_ and
- `D101615 <https://reviews.llvm.org/D101615>`_)
-
-* Fix automatic export of symbols from LTO objects.
- (`D101569 <https://reviews.llvm.org/D101569>`_)
-
-* Accept more spellings of some options.
- (`D107237 <https://reviews.llvm.org/D107237>`_ and
- `D107253 <https://reviews.llvm.org/D107253>`_)
-
-Mach-O Improvements
--------------------
-
-The Mach-O backend is now able to link several large, real-world programs,
-though we are still working out the kinks.
-
-* arm64 is now supported as a target. (`D88629 <https://reviews.llvm.org/D88629>`_)
-* arm64_32 is now supported as a target. (`D99822 <https://reviews.llvm.org/D99822>`_)
-* Branch-range-extension thunks are now supported. (`D100818 <https://reviews.llvm.org/D100818>`_)
-* ``-dead_strip`` is now supported. (`D103324 <https://reviews.llvm.org/D103324>`_)
-* Support for identical code folding (``--icf=all``) has been added.
- (`D103292 <https://reviews.llvm.org/D103292>`_)
-* Support for special ``$start`` and ``$end`` symbols for segment & sections has been
- added. (`D106767 <https://reviews.llvm.org/D106767>`_, `D106629 <https://reviews.llvm.org/D106629>`_)
-* ``$ld$previous`` symbols are now supported. (`D103505 <https://reviews.llvm.org/D103505 >`_)
-* ``$ld$install_name`` symbols are now supported. (`D103746 <https://reviews.llvm.org/D103746>`_)
-* ``__mh_*_header`` symbols are now supported. (`D97007 <https://reviews.llvm.org/D97007>`_)
-* LC_CODE_SIGNATURE is now supported. (`D96164 <https://reviews.llvm.org/D96164>`_)
-* LC_FUNCTION_STARTS is now supported. (`D97260 <https://reviews.llvm.org/D97260>`_)
-* LC_DATA_IN_CODE is now supported. (`D103006 <https://reviews.llvm.org/D103006>`_)
-* Bind opcodes are more compactly encoded. (`D106128 <https://reviews.llvm.org/D106128>`_,
- `D105075 <https://reviews.llvm.org/D105075>`_)
-* LTO cache support has been added. (`D105922 <https://reviews.llvm.org/D105922>`_)
-* ``-application_extension`` is now supported. (`D105818 <https://reviews.llvm.org/D105818>`_)
-* ``-export_dynamic`` is now partially supported. (`D105482 <https://reviews.llvm.org/D105482>`_)
-* ``-arch_multiple`` is now supported. (`D105450 <https://reviews.llvm.org/D105450>`_)
-* ``-final_output`` is now supported. (`D105449 <https://reviews.llvm.org/D105449>`_)
-* ``-umbrella`` is now supported. (`D105448 <https://reviews.llvm.org/D105448>`_)
-* ``--print-dylib-search`` is now supported. (`D103985 <https://reviews.llvm.org/D103985>`_)
-* ``-force_load_swift_libs`` is now supported. (`D103709 <https://reviews.llvm.org/D103709>`_)
-* ``-reexport_framework``, ``-reexport_library``, ``-reexport-l`` are now supported.
- (`D103497 <https://reviews.llvm.org/D103497>`_)
-* ``.weak_def_can_be_hidden`` is now supported. (`D101080 <https://reviews.llvm.org/D101080>`_)
-* ``-add_ast_path`` is now supported. (`D100076 <https://reviews.llvm.org/D100076>`_)
-* ``-segprot`` is now supported. (`D99389 <https://reviews.llvm.org/D99389>`_)
-* ``-dependency_info`` is now partially supported. (`D98559 <https://reviews.llvm.org/D98559>`_)
-* ``--time-trace`` is now supported. (`D98419 <https://reviews.llvm.org/D98419>`_)
-* ``-mark_dead_strippable_dylib`` is now supported. (`D98262 <https://reviews.llvm.org/D98262>`_)
-* ``-[un]exported_symbol[s_list]`` is now supported. (`D98223 <https://reviews.llvm.org/D98223>`_)
-* ``-flat_namespace`` is now supported. (`D97641 <https://reviews.llvm.org/D97641>`_)
-* ``-rename_section`` and ``-rename_segment`` are now supported. (`D97600 <https://reviews.llvm.org/D97600>`_)
-* ``-bundle_loader`` is now supported. (`D95913 <https://reviews.llvm.org/D95913>`_)
-* ``-map`` is now partially supported. (`D98323 <https://reviews.llvm.org/D98323>`_)
-
-There were numerous other bug-fixes as well.
+* The lld-specific options ``--guard-cf``, ``--no-guard-cf``,
+ ``--guard-longjmp`` and ``--no-guard-longjmp`` has been added to allow
+ enabling Control Flow Guard and long jump hardening. These options are
+ disabled by default, but enabling ``--guard-cf`` will also enable
+ ``--guard-longjmp`` unless ``--no-guard-longjmp`` is also specified.
+ ``--guard-longjmp`` depends on ``--guard-cf`` and cannot be used by itself.
+ Note that these features require the ``_load_config_used`` symbol to contain
+ the load config directory and be filled with the required symbols.
+ (`D132808 <https://reviews.llvm.org/D132808>`_)
+
+* Pick up libraries named ``<name>.lib`` when linked with ``-l<name>``, even
+ if ``-static`` has been specified. This fixes conformance to what
+ GNU ld does. (`D135651 <https://reviews.llvm.org/D135651>`_)
+
+* Unwinding in Rust code on i386 in MinGW builds has been fixed, by avoiding
+ to leave out the ``rust_eh_personality`` symbol.
+ (`D136879 <https://reviews.llvm.org/D136879>`_)
+
+MachO Improvements
+------------------
WebAssembly Improvements
------------------------
Usage
-----
-The WebAssembly version of lld is installed as **wasm-ld**. It shared many
+The WebAssembly version of lld is installed as **wasm-ld**. It shared many
common linker flags with **ld.lld** but also includes several
WebAssembly-specific options:
flag which corresponds to ``--unresolve-symbols=ignore`` +
``--import-undefined``.
+.. option:: --allow-undefined-file=<filename>
+
+ Like ``--allow-undefined``, but the filename specified a flat list of
+ symbols, one per line, which are allowed to be undefined.
+
.. option:: --unresolved-symbols=<method>
This is a more full featured version of ``--allow-undefined``.
this is trivial. For direct function calls, the linker will generate a
trapping stub function in place of the undefined function.
+ import-dynamic:
+
+ Undefined symbols generate WebAssembly imports, including undefined data
+ symbols. This is somewhat similar to the --import-undefined option but
+ works all symbol types. This options puts limitations on the type of
+ relocations that are allowed for imported data symbols. Relocations that
+ require absolute data addresses (i.e. All R_WASM_MEMORY_ADDR_I32) will
+ generate an error if they cannot be resolved statically. For clang/llvm
+ this means inputs should be compiled with `-fPIC` (i.e. `pic` or
+ `dynamic-no-pic` relocation models). This options is useful for linking
+ binaries that are themselves static (non-relocatable) but whose undefined
+ symbols are resolved by a dynamic linker. Since the dynamic linking API is
+ experimental, this option currently requires `--experimental-pic` to also
+ be specified.
+
.. option:: --import-memory
Import memory from the environment.
By default no undefined symbols are allowed in the final binary. The flag
``--allow-undefined`` results in a WebAssembly import being defined for each
undefined symbol. It is then up to the runtime to provide such symbols.
+``--allow-undefined-file`` is the same but allows a list of symbols to be
+specified.
Alternatively symbols can be marked in the source code as with the
``import_name`` and/or ``import_module`` clang attributes which signals that
they are expected to be undefined at static link time.
+Stub Libraries
+~~~~~~~~~~~~~~
+
+Another way to specify imports and exports is via a "stub library". This
+feature is inspired by the ELF stub objects which are supported by the Solaris
+linker. Stub libraries are text files that can be passed as normal linker
+inputs, similar to how linker scripts can be passed to the ELF linker. The stub
+library is a stand-in for a set of symbols that will be available at runtime,
+but doesn't contain any actual code or data. Instead it contains just a list of
+symbols, one per line. Each symbol can specify zero or more dependencies.
+These dependencies are symbols that must be defined, and exported, by the output
+module if the symbol is question is imported/required by the output module.
+
+For example, imagine the runtime provides an external symbol ``foo`` that
+depends on the ``malloc`` and ``free``. This can be expressed simply as::
+
+ #STUB
+ foo: malloc,free
+
+Here we are saying that ``foo`` is allowed to be imported (undefined) but that
+if it is imported, then the output module must also export ``malloc`` and
+``free`` to the runtime. If ``foo`` is imported (undefined), but the output
+module does not define ``malloc`` and ``free`` then the link will fail.
+
+Stub libraries must begin with ``#STUB`` on a line by itself.
+
Garbage Collection
~~~~~~~~~~~~~~~~~~
supported.
- No support for creating shared libraries. The spec for shared libraries in
WebAssembly is still in flux:
- https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md
+ https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md
-.. _linking: https://github.com/WebAssembly/tool-conventions/blob/master/Linking.md
+.. _linking: https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md
<h3>Bugs</h3>
-<p>lld bugs should be reported at the
- LLVM <a href="https://bugs.llvm.org/">Bugzilla</a>.</p>
+<p>
+To report bugs, please visit
+<a href="https://github.com/llvm/llvm-project/labels/lld:COFF">PE/COFF</a>,
+<a href="https://github.com/llvm/llvm-project/labels/lld:ELF">ELF</a>,
+<a href="https://github.com/llvm/llvm-project/labels/lld:MachO">Mach-O</a>, or
+<a href="https://github.com/llvm/llvm-project/labels/lld:wasm">WebAssembly</a>.
+</p>
project = u'lld'
copyright = u'2011-%d, LLVM Project' % date.today().year
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short version.
-version = '13'
-# The full version, including alpha/beta/rc tags.
-release = '13'
-
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
several different linkers. The ELF port is the one that will be described in
this document. The PE/COFF port is complete, including
Windows debug info (PDB) support. The WebAssembly port is still a work in
-progress (See :doc:`WebAssembly`). The Mach-O port is built based on a
-different architecture than the others. For the details about Mach-O, please
-read :doc:`AtomLLD`.
+progress (See :doc:`WebAssembly`).
Features
--------
- LLD is a drop-in replacement for the GNU linkers that accepts the
same command line arguments and linker scripts as GNU.
- We are currently working closely with the FreeBSD project to make
- LLD default system linker in future versions of the operating
- system, so we are serious about addressing compatibility issues. As
- of February 2017, LLD is able to link the entire FreeBSD/amd64 base
- system including the kernel. With a few work-in-progress patches it
- can link approximately 95% of the ports collection on AMD64. For the
- details, see `FreeBSD quarterly status report
- <https://www.freebsd.org/news/status/report-2016-10-2016-12.html#Using-LLVM%27s-LLD-Linker-as-FreeBSD%27s-System-Linker>`_.
-
- LLD is very fast. When you link a large program on a multicore
machine, you can expect that LLD runs more than twice as fast as the GNU
gold linker. Your mileage may vary, though.
:maxdepth: 1
NewLLD
- AtomLLD
WebAssembly
windows_support
missingkeyfunction
Partitions
ReleaseNotes
ELF/linker_script
+ ELF/start-stop-gc
ELF/warn_backrefs
+ MachO/index
int64_t getHex(llvm::opt::InputArgList &args, unsigned key, int64_t Default);
-std::vector<StringRef> getStrings(llvm::opt::InputArgList &args, int id);
+llvm::SmallVector<StringRef, 0> getStrings(llvm::opt::InputArgList &args,
+ int id);
uint64_t getZOptionValue(llvm::opt::InputArgList &args, int id, StringRef key,
uint64_t Default);
--- /dev/null
+//===- CommonLinkerContext.h ------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Entry point for all global state in lldCommon. The objective is for LLD to be
+// used "as a library" in a thread-safe manner.
+//
+// Instead of program-wide globals or function-local statics, we prefer
+// aggregating all "global" states into a heap-based structure
+// (CommonLinkerContext). This also achieves deterministic initialization &
+// shutdown for all "global" states.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLD_COMMON_COMMONLINKINGCONTEXT_H
+#define LLD_COMMON_COMMONLINKINGCONTEXT_H
+
+#include "lld/Common/ErrorHandler.h"
+#include "lld/Common/Memory.h"
+#include "llvm/Support/StringSaver.h"
+
+namespace llvm {
+class raw_ostream;
+} // namespace llvm
+
+namespace lld {
+struct SpecificAllocBase;
+class CommonLinkerContext {
+public:
+ CommonLinkerContext();
+ virtual ~CommonLinkerContext();
+
+ static void destroy();
+
+ llvm::BumpPtrAllocator bAlloc;
+ llvm::StringSaver saver{bAlloc};
+ llvm::DenseMap<void *, SpecificAllocBase *> instances;
+
+ ErrorHandler e;
+};
+
+// Retrieve the global state. Currently only one state can exist per process,
+// but in the future we plan on supporting an arbitrary number of LLD instances
+// in a single process.
+CommonLinkerContext &commonContext();
+
+template <typename T = CommonLinkerContext> T &context() {
+ return static_cast<T &>(commonContext());
+}
+
+bool hasContext();
+
+inline llvm::StringSaver &saver() { return context().saver; }
+inline llvm::BumpPtrAllocator &bAlloc() { return context().bAlloc; }
+} // namespace lld
+
+#endif
class DWARFCache {
public:
DWARFCache(std::unique_ptr<llvm::DWARFContext> dwarf);
- llvm::Optional<llvm::DILineInfo> getDILineInfo(uint64_t offset,
- uint64_t sectionIndex);
- llvm::Optional<std::pair<std::string, unsigned>>
+ std::optional<llvm::DILineInfo> getDILineInfo(uint64_t offset,
+ uint64_t sectionIndex);
+ std::optional<std::pair<std::string, unsigned>>
getVariableLoc(StringRef name);
llvm::DWARFContext *getContext() { return dwarf.get(); }
llvm::raw_ostream &stderrOS);
namespace coff {
-bool link(llvm::ArrayRef<const char *> args, bool canExitEarly,
- llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS);
+bool link(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput);
}
namespace mingw {
-bool link(llvm::ArrayRef<const char *> args, bool canExitEarly,
- llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS);
+bool link(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput);
}
namespace elf {
-bool link(llvm::ArrayRef<const char *> args, bool canExitEarly,
- llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS);
-}
-
-namespace mach_o {
-bool link(llvm::ArrayRef<const char *> args, bool canExitEarly,
- llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS);
+bool link(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput);
}
namespace macho {
-bool link(llvm::ArrayRef<const char *> args, bool canExitEarly,
- llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS);
+bool link(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput);
}
namespace wasm {
-bool link(llvm::ArrayRef<const char *> args, bool canExitEarly,
- llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS);
-}
+bool link(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput);
}
+} // namespace lld
#endif
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileOutputBuffer.h"
+#include <mutex>
namespace llvm {
class DiagnosticInfo;
namespace lld {
-// We wrap stdout and stderr so that you can pass alternative stdout/stderr as
-// arguments to lld::*::link() functions.
-extern llvm::raw_ostream *stdoutOS;
-extern llvm::raw_ostream *stderrOS;
-
llvm::raw_ostream &outs();
llvm::raw_ostream &errs();
class ErrorHandler {
public:
+ ~ErrorHandler();
+
+ void initialize(llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS,
+ bool exitEarly, bool disableOutput);
+
uint64_t errorCount = 0;
uint64_t errorLimit = 20;
StringRef errorLimitExceededMsg = "too many errors emitted, stopping now";
StringRef logName = "lld";
bool exitEarly = true;
bool fatalWarnings = false;
+ bool suppressWarnings = false;
bool verbose = false;
bool vsDiagnostics = false;
bool disableOutput = false;
void error(const Twine &msg, ErrorTag tag, ArrayRef<StringRef> args);
[[noreturn]] void fatal(const Twine &msg);
void log(const Twine &msg);
- void message(const Twine &msg);
+ void message(const Twine &msg, llvm::raw_ostream &s);
void warn(const Twine &msg);
- void reset() {
- if (cleanupCallback)
- cleanupCallback();
- *this = ErrorHandler();
- }
+ raw_ostream &outs();
+ raw_ostream &errs();
+ void flushStreams();
std::unique_ptr<llvm::FileOutputBuffer> outputBuffer;
using Colors = raw_ostream::Colors;
std::string getLocation(const Twine &msg);
+ void reportDiagnostic(StringRef location, Colors c, StringRef diagKind,
+ const Twine &msg);
+
+ // We want to separate multi-line messages with a newline. `sep` is "\n"
+ // if the last messages was multi-line. Otherwise "".
+ llvm::StringRef sep;
+
+ // We wrap stdout and stderr so that you can pass alternative stdout/stderr as
+ // arguments to lld::*::link() functions. Since lld::outs() or lld::errs() can
+ // be indirectly called from multiple threads, we protect them using a mutex.
+ // In the future, we plan on supporting several concurrent linker contexts,
+ // which explains why the mutex is not a global but part of this context.
+ std::mutex mu;
+ llvm::raw_ostream *stdoutOS{};
+ llvm::raw_ostream *stderrOS{};
};
/// Returns the default error handler.
ErrorHandler &errorHandler();
-inline void error(const Twine &msg) { errorHandler().error(msg); }
-inline void error(const Twine &msg, ErrorTag tag, ArrayRef<StringRef> args) {
- errorHandler().error(msg, tag, args);
-}
-[[noreturn]] inline void fatal(const Twine &msg) { errorHandler().fatal(msg); }
-inline void log(const Twine &msg) { errorHandler().log(msg); }
-inline void message(const Twine &msg) { errorHandler().message(msg); }
-inline void warn(const Twine &msg) { errorHandler().warn(msg); }
-inline uint64_t errorCount() { return errorHandler().errorCount; }
+void error(const Twine &msg);
+void error(const Twine &msg, ErrorTag tag, ArrayRef<StringRef> args);
+[[noreturn]] void fatal(const Twine &msg);
+void log(const Twine &msg);
+void message(const Twine &msg, llvm::raw_ostream &s = outs());
+void warn(const Twine &msg);
+uint64_t errorCount();
[[noreturn]] void exitLld(int val);
namespace wasm {
struct WasmTag;
-struct WasmTagType;
struct WasmFunction;
struct WasmGlobal;
struct WasmGlobalType;
using llvm::wasm::WasmTable;
using llvm::wasm::WasmTableType;
using llvm::wasm::WasmTag;
-using llvm::wasm::WasmTagType;
} // end namespace lld.
namespace std {
#define LLD_COMMON_MEMORY_H
#include "llvm/Support/Allocator.h"
-#include "llvm/Support/StringSaver.h"
-#include <vector>
namespace lld {
-
-// Use this arena if your object doesn't have a destructor.
-extern llvm::BumpPtrAllocator bAlloc;
-extern llvm::StringSaver saver;
-
-void freeArena();
-
-// These two classes are hack to keep track of all
-// SpecificBumpPtrAllocator instances.
+// A base class only used by the CommonLinkerContext to keep track of the
+// SpecificAlloc<> instances.
struct SpecificAllocBase {
- SpecificAllocBase() { instances.push_back(this); }
virtual ~SpecificAllocBase() = default;
- virtual void reset() = 0;
- static std::vector<SpecificAllocBase *> instances;
+ static SpecificAllocBase *getOrCreate(void *tag, size_t size, size_t align,
+ SpecificAllocBase *(&creator)(void *));
};
+// An arena of specific types T, created on-demand.
template <class T> struct SpecificAlloc : public SpecificAllocBase {
- void reset() override { alloc.DestroyAll(); }
+ static SpecificAllocBase *create(void *storage) {
+ return new (storage) SpecificAlloc<T>();
+ }
llvm::SpecificBumpPtrAllocator<T> alloc;
+ static int tag;
};
-// Use a static local for these singletons so they are only registered if an
-// object of this instance is ever constructed. Otherwise we will create and
-// register ELF allocators for COFF and the reverse.
+// The address of this static member is only used as a key in
+// CommonLinkerContext::instances. Its value does not matter.
+template <class T> int SpecificAlloc<T>::tag = 0;
+
+// Creates the arena on-demand on the first call; or returns it, if it was
+// already created.
template <typename T>
inline llvm::SpecificBumpPtrAllocator<T> &getSpecificAllocSingleton() {
- static SpecificAlloc<T> instance;
- return instance.alloc;
+ SpecificAllocBase *instance = SpecificAllocBase::getOrCreate(
+ &SpecificAlloc<T>::tag, sizeof(SpecificAlloc<T>),
+ alignof(SpecificAlloc<T>), SpecificAlloc<T>::create);
+ return ((SpecificAlloc<T> *)instance)->alloc;
}
-// Use this arena if your object has a destructor.
-// Your destructor will be invoked from freeArena().
+// Creates new instances of T off a (almost) contiguous arena/object pool. The
+// instances are destroyed whenever lldMain() goes out of scope.
template <typename T, typename... U> T *make(U &&... args) {
return new (getSpecificAllocSingleton<T>().Allocate())
T(std::forward<U>(args)...);
}
+template <typename T>
+inline llvm::SpecificBumpPtrAllocator<T> &
+getSpecificAllocSingletonThreadLocal() {
+ thread_local SpecificAlloc<T> instance;
+ return instance.alloc;
+}
+
+// Create a new instance of T off a thread-local SpecificAlloc, used by code
+// like parallel input section initialization. The use cases assume that the
+// return value outlives the containing parallelForEach (if exists), which is
+// currently guaranteed: when parallelForEach returns, the threads allocating
+// the TLS are not destroyed.
+//
+// Note: Some ports (e.g. ELF) have lots of global states which are currently
+// infeasible to remove, and context() just adds overhead with no benefit. The
+// allocation performance is of higher importance, so we simply use thread_local
+// allocators instead of doing context indirection and pthread_getspecific.
+template <typename T, typename... U> T *makeThreadLocal(U &&...args) {
+ return new (getSpecificAllocSingletonThreadLocal<T>().Allocate())
+ T(std::forward<U>(args)...);
+}
+
+template <typename T> T *makeThreadLocalN(size_t n) {
+ return new (getSpecificAllocSingletonThreadLocal<T>().Allocate(n)) T[n];
+}
+
} // namespace lld
#endif
#define LLD_STRINGS_H
#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/GlobPattern.h"
#include <string>
#include <vector>
namespace lld {
-// Returns a demangled C++ symbol name. If Name is not a mangled
-// name, it returns name.
-std::string demangleItanium(llvm::StringRef name);
-std::vector<uint8_t> parseHex(llvm::StringRef s);
+llvm::SmallVector<uint8_t, 0> parseHex(llvm::StringRef s);
bool isValidCIdentifier(llvm::StringRef s);
// Write the contents of the a buffer to a file
#ifndef LLD_COMMON_TARGETOPTIONSCOMMANDFLAGS_H
#define LLD_COMMON_TARGETOPTIONSCOMMANDFLAGS_H
-#include "llvm/ADT/Optional.h"
#include "llvm/Support/CodeGen.h"
#include "llvm/Target/TargetOptions.h"
+#include <optional>
namespace lld {
llvm::TargetOptions initTargetOptionsFromCodeGenFlags();
-llvm::Optional<llvm::Reloc::Model> getRelocModelFromCMModel();
-llvm::Optional<llvm::CodeModel::Model> getCodeModelFromCMModel();
+std::optional<llvm::Reloc::Model> getRelocModelFromCMModel();
+std::optional<llvm::CodeModel::Model> getCodeModelFromCMModel();
std::string getCPUStr();
std::vector<std::string> getMAttrs();
}
public:
Timer(llvm::StringRef name, Timer &parent);
- static Timer &root();
+ // Creates the root timer.
+ explicit Timer(llvm::StringRef name);
void addToTotal(std::chrono::nanoseconds time) { total += time.count(); }
void print();
double millis() const;
private:
- explicit Timer(llvm::StringRef name);
void print(int depth, double totalDuration, bool recurse = true) const;
std::atomic<std::chrono::nanoseconds::rep> total;
set(LLVM_LINK_COMPONENTS
Support
+ TargetParser
)
add_lld_tool(lld
lld.cpp
SUPPORT_PLUGINS
+ GENERATE_DRIVER
)
export_executable_symbols_for_plugins(lld)
-target_link_libraries(lld
+function(lld_target_link_libraries target type)
+ target_link_libraries(${target} ${type} ${ARGN})
+ if (TARGET obj.${target})
+ target_link_libraries(obj.${target} ${ARGN})
+ endif()
+endfunction()
+
+lld_target_link_libraries(lld
PRIVATE
lldCommon
lldCOFF
- lldDriver
lldELF
- lldMachO2
+ lldMachO
lldMinGW
lldWasm
)
install(TARGETS lld
- RUNTIME DESTINATION bin)
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
if(NOT LLD_SYMLINKS_TO_CREATE)
set(LLD_SYMLINKS_TO_CREATE
- lld-link ld.lld ld64.lld ld64.lld.darwinnew ld64.lld.darwinold wasm-ld)
+ lld-link ld.lld ld64.lld wasm-ld)
endif()
foreach(link ${LLD_SYMLINKS_TO_CREATE})
Option
Passes
Support
+ TargetParser
LINK_LIBS
lldCommon
#ifndef LLD_WASM_CONFIG_H
#define LLD_WASM_CONFIG_H
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/BinaryFormat/Wasm.h"
#include "llvm/Support/CachePruning.h"
+#include <optional>
namespace lld {
namespace wasm {
+class InputFile;
+class Symbol;
+
// For --unresolved-symbols.
-enum class UnresolvedPolicy { ReportError, Warn, Ignore };
+enum class UnresolvedPolicy { ReportError, Warn, Ignore, ImportDynamic };
// This struct contains the global configuration for the linker.
// Most fields are direct mapping from the command line options
bool exportAll;
bool exportDynamic;
bool exportTable;
+ bool extendedConst;
bool growableTable;
bool gcSections;
- bool importMemory;
+ std::optional<std::pair<llvm::StringRef, llvm::StringRef>> memoryImport;
+ std::optional<llvm::StringRef> memoryExport;
bool sharedMemory;
bool importTable;
bool importUndefined;
- llvm::Optional<bool> is64;
+ std::optional<bool> is64;
bool mergeDataSegments;
bool pie;
bool printGcSections;
bool stripAll;
bool stripDebug;
bool stackFirst;
+ bool isStatic = false;
bool trace;
uint64_t globalBase;
uint64_t initialMemory;
unsigned ltoo;
unsigned optimize;
llvm::StringRef thinLTOJobs;
- bool ltoNewPassManager;
bool ltoDebugPassManager;
UnresolvedPolicy unresolvedSymbols;
llvm::StringRef mapFile;
llvm::StringRef outputFile;
llvm::StringRef thinLTOCacheDir;
+ llvm::StringRef whyExtract;
llvm::StringSet<> allowUndefinedSymbols;
llvm::StringSet<> exportedSymbols;
std::vector<llvm::StringRef> requiredExports;
- std::vector<llvm::StringRef> searchPaths;
+ llvm::SmallVector<llvm::StringRef, 0> searchPaths;
llvm::CachePruningPolicy thinLTOCachePolicy;
- llvm::Optional<std::vector<std::string>> features;
+ std::optional<std::vector<std::string>> features;
+ std::optional<std::vector<std::string>> extraFeatures;
// The following config options do not directly correspond to any
- // particualr command line options.
+ // particular command line options, and should probably be moved to seperate
+ // Ctx struct as in ELF/Config.h
// True if we are creating position-independent code.
bool isPic;
// for shared libraries (since they always added to a dynamic offset at
// runtime).
uint32_t tableBase = 0;
+
+ // Will be set to true if bss data segments should be emitted. In most cases
+ // this is not necessary.
+ bool emitBssSegments = false;
+
+ // A tuple of (reference, extractedFile, sym). Used by --why-extract=.
+ llvm::SmallVector<std::tuple<std::string, const InputFile *, const Symbol &>,
+ 0>
+ whyExtractRecords;
};
// The only instance of Configuration struct.
#include "SymbolTable.h"
#include "Writer.h"
#include "lld/Common/Args.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Filesystem.h"
#include "lld/Common/Memory.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/TarWriter.h"
#include "llvm/Support/TargetSelect.h"
+#include <optional>
#define DEBUG_TYPE "lld"
};
} // anonymous namespace
-bool link(ArrayRef<const char *> args, bool canExitEarly, raw_ostream &stdoutOS,
- raw_ostream &stderrOS) {
- lld::stdoutOS = &stdoutOS;
- lld::stderrOS = &stderrOS;
+bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
+ // This driver-specific context will be freed later by lldMain().
+ auto *ctx = new CommonLinkerContext;
- errorHandler().cleanupCallback = []() { freeArena(); };
-
- errorHandler().logName = args::getFilenameWithoutExe(args[0]);
- errorHandler().errorLimitExceededMsg =
- "too many errors emitted, stopping now (use "
- "-error-limit=0 to see all errors)";
- stderrOS.enable_colors(stderrOS.has_colors());
+ ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
+ ctx->e.logName = args::getFilenameWithoutExe(args[0]);
+ ctx->e.errorLimitExceededMsg = "too many errors emitted, stopping now (use "
+ "-error-limit=0 to see all errors)";
config = make<Configuration>();
symtab = make<SymbolTable>();
initLLVM();
LinkerDriver().linkerMain(args);
- // Exit immediately if we don't need to return to the caller.
- // This saves time because the overhead of calling destructors
- // for all globally-allocated objects is not negligible.
- if (canExitEarly)
- exitLld(errorCount() ? 1 : 0);
-
- return !errorCount();
+ return errorCount() == 0;
}
// Create prefix string literals used in Options.td
-#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
+#define PREFIX(NAME, VALUE) \
+ static constexpr StringLiteral NAME##_init[] = VALUE; \
+ static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \
+ std::size(NAME##_init) - 1);
#include "Options.inc"
#undef PREFIX
// Create table mapping all options defined in Options.td
-static const opt::OptTable::Info optInfo[] = {
+static constexpr opt::OptTable::Info optInfo[] = {
#define OPTION(X1, X2, ID, KIND, GROUP, ALIAS, X7, X8, X9, X10, X11, X12) \
{X1, X2, X10, X11, OPT_##ID, opt::Option::KIND##Class, \
X9, X8, OPT_##GROUP, OPT_##ALIAS, X7, X12},
};
namespace {
-class WasmOptTable : public llvm::opt::OptTable {
+class WasmOptTable : public opt::GenericOptTable {
public:
- WasmOptTable() : OptTable(optInfo) {}
+ WasmOptTable() : opt::GenericOptTable(optInfo) {}
opt::InputArgList parse(ArrayRef<const char *> argv);
};
} // namespace
}
// Find a file by concatenating given paths.
-static Optional<std::string> findFile(StringRef path1, const Twine &path2) {
+static std::optional<std::string> findFile(StringRef path1,
+ const Twine &path2) {
SmallString<128> s;
path::append(s, path1, path2);
if (fs::exists(s))
return std::string(s);
- return None;
+ return std::nullopt;
}
opt::InputArgList WasmOptTable::parse(ArrayRef<const char *> argv) {
// Expand response files (arguments in the form of @<filename>)
// and then parse the argument again.
- cl::ExpandResponseFiles(saver, getQuotingStyle(args), vec);
+ cl::ExpandResponseFiles(saver(), getQuotingStyle(args), vec);
args = this->ParseArgs(vec, missingIndex, missingCount);
handleColorDiagnostics(args);
+ if (missingCount)
+ error(Twine(args.getArgString(missingIndex)) + ": missing argument");
+
for (auto *arg : args.filtered(OPT_UNKNOWN))
error("unknown argument: " + arg->getAsString(args));
return args;
// attribute/flag in the object file itself.
// See: https://github.com/WebAssembly/tool-conventions/issues/35
static void readImportFile(StringRef filename) {
- if (Optional<MemoryBufferRef> buf = readFile(filename))
+ if (std::optional<MemoryBufferRef> buf = readFile(filename))
for (StringRef sym : args::getLines(*buf))
config->allowUndefinedSymbols.insert(sym);
}
}
void LinkerDriver::addFile(StringRef path) {
- Optional<MemoryBufferRef> buffer = readFile(path);
- if (!buffer.hasValue())
+ std::optional<MemoryBufferRef> buffer = readFile(path);
+ if (!buffer)
return;
MemoryBufferRef mbref = *buffer;
case file_magic::wasm_object:
files.push_back(createObjectFile(mbref));
break;
+ case file_magic::unknown:
+ if (mbref.getBuffer().starts_with("#STUB")) {
+ files.push_back(make<StubFile>(mbref));
+ break;
+ }
+ [[fallthrough]];
default:
error("unknown file type: " + mbref.getBufferIdentifier());
}
}
-// Add a given library by searching it from input search paths.
-void LinkerDriver::addLibrary(StringRef name) {
+static std::optional<std::string> findFromSearchPaths(StringRef path) {
+ for (StringRef dir : config->searchPaths)
+ if (std::optional<std::string> s = findFile(dir, path))
+ return s;
+ return std::nullopt;
+}
+
+// This is for -l<basename>. We'll look for lib<basename>.a from
+// search paths.
+static std::optional<std::string> searchLibraryBaseName(StringRef name) {
for (StringRef dir : config->searchPaths) {
- if (Optional<std::string> s = findFile(dir, "lib" + name + ".a")) {
- addFile(*s);
- return;
- }
+ // Currently we don't enable dyanmic linking at all unless -shared or -pie
+ // are used, so don't even look for .so files in that case..
+ if (config->isPic && !config->isStatic)
+ if (std::optional<std::string> s = findFile(dir, "lib" + name + ".so"))
+ return s;
+ if (std::optional<std::string> s = findFile(dir, "lib" + name + ".a"))
+ return s;
}
+ return std::nullopt;
+}
+
+// This is for -l<namespec>.
+static std::optional<std::string> searchLibrary(StringRef name) {
+ if (name.startswith(":"))
+ return findFromSearchPaths(name.substr(1));
+ return searchLibraryBaseName(name);
+}
- error("unable to find library -l" + name);
+// Add a given library by searching it from input search paths.
+void LinkerDriver::addLibrary(StringRef name) {
+ if (std::optional<std::string> path = searchLibrary(name))
+ addFile(saver().save(*path));
+ else
+ error("unable to find library -l" + name, ErrorTag::LibNotFound, {name});
}
void LinkerDriver::createFiles(opt::InputArgList &args) {
for (auto *arg : args) {
switch (arg->getOption().getID()) {
- case OPT_l:
+ case OPT_library:
addLibrary(arg->getValue());
break;
case OPT_INPUT:
addFile(arg->getValue());
break;
+ case OPT_Bstatic:
+ config->isStatic = true;
+ break;
+ case OPT_Bdynamic:
+ config->isStatic = false;
+ break;
case OPT_whole_archive:
inWholeArchive = true;
break;
StringRef s = arg->getValue();
if (s == "ignore-all")
return UnresolvedPolicy::Ignore;
+ if (s == "import-dynamic")
+ return UnresolvedPolicy::ImportDynamic;
if (s == "report-all")
return errorOrWarn;
error("unknown --unresolved-symbols value: " + s);
config->exportAll = args.hasArg(OPT_export_all);
config->exportTable = args.hasArg(OPT_export_table);
config->growableTable = args.hasArg(OPT_growable_table);
- errorHandler().fatalWarnings =
- args.hasFlag(OPT_fatal_warnings, OPT_no_fatal_warnings, false);
- config->importMemory = args.hasArg(OPT_import_memory);
+
+ if (args.hasArg(OPT_import_memory_with_name)) {
+ config->memoryImport =
+ args.getLastArgValue(OPT_import_memory_with_name).split(",");
+ } else if (args.hasArg(OPT_import_memory)) {
+ config->memoryImport =
+ std::pair<llvm::StringRef, llvm::StringRef>(defaultModule, memoryName);
+ } else {
+ config->memoryImport =
+ std::optional<std::pair<llvm::StringRef, llvm::StringRef>>();
+ }
+
+ if (args.hasArg(OPT_export_memory_with_name)) {
+ config->memoryExport =
+ args.getLastArgValue(OPT_export_memory_with_name);
+ } else if (args.hasArg(OPT_export_memory)) {
+ config->memoryExport = memoryName;
+ } else {
+ config->memoryExport = std::optional<llvm::StringRef>();
+ }
+
config->sharedMemory = args.hasArg(OPT_shared_memory);
config->importTable = args.hasArg(OPT_import_table);
config->importUndefined = args.hasArg(OPT_import_undefined);
config->ltoo = args::getInteger(args, OPT_lto_O, 2);
config->ltoPartitions = args::getInteger(args, OPT_lto_partitions, 1);
- config->ltoNewPassManager =
- args.hasFlag(OPT_no_lto_legacy_pass_manager, OPT_lto_legacy_pass_manager,
- LLVM_ENABLE_NEW_PASS_MANAGER);
config->ltoDebugPassManager = args.hasArg(OPT_lto_debug_pass_manager);
config->mapFile = args.getLastArgValue(OPT_Map);
config->optimize = args::getInteger(args, OPT_O, 1);
config->printGcSections =
args.hasFlag(OPT_print_gc_sections, OPT_no_print_gc_sections, false);
config->saveTemps = args.hasArg(OPT_save_temps);
- config->searchPaths = args::getStrings(args, OPT_L);
+ config->searchPaths = args::getStrings(args, OPT_library_path);
config->shared = args.hasArg(OPT_shared);
config->stripAll = args.hasArg(OPT_strip_all);
config->stripDebug = args.hasArg(OPT_strip_debug);
parseCachePruningPolicy(args.getLastArgValue(OPT_thinlto_cache_policy)),
"--thinlto-cache-policy: invalid cache policy");
config->unresolvedSymbols = getUnresolvedSymbolPolicy(args);
+ config->whyExtract = args.getLastArgValue(OPT_why_extract);
errorHandler().verbose = args.hasArg(OPT_verbose);
LLVM_DEBUG(errorHandler().verbose = true);
config->initialMemory = args::getInteger(args, OPT_initial_memory, 0);
- config->globalBase = args::getInteger(args, OPT_global_base, 1024);
+ config->globalBase = args::getInteger(args, OPT_global_base, 0);
config->maxMemory = args::getInteger(args, OPT_max_memory, 0);
config->zStackSize =
args::getZOptionValue(args, OPT_z, "stack-size", WasmPageSize);
if (auto *arg = args.getLastArg(OPT_features)) {
config->features =
- llvm::Optional<std::vector<std::string>>(std::vector<std::string>());
+ std::optional<std::vector<std::string>>(std::vector<std::string>());
for (StringRef s : arg->getValues())
config->features->push_back(std::string(s));
}
+ if (auto *arg = args.getLastArg(OPT_extra_features)) {
+ config->extraFeatures =
+ std::optional<std::vector<std::string>>(std::vector<std::string>());
+ for (StringRef s : arg->getValues())
+ config->extraFeatures->push_back(std::string(s));
+ }
+
// Legacy --allow-undefined flag which is equivalent to
// --unresolve-symbols=ignore + --import-undefined
if (args.hasArg(OPT_allow_undefined)) {
}
if (config->shared) {
- config->importMemory = true;
+ if (config->memoryExport.has_value()) {
+ error("--export-memory is incompatible with --shared");
+ }
+ if (!config->memoryImport.has_value()) {
+ config->memoryImport =
+ std::pair<llvm::StringRef, llvm::StringRef>(defaultModule, memoryName);
+ }
config->importUndefined = true;
- config->unresolvedSymbols = UnresolvedPolicy::Ignore;
+ }
+
+ // If neither export-memory nor import-memory is specified, default to
+ // exporting memory under its default name.
+ if (!config->memoryExport.has_value() && !config->memoryImport.has_value()) {
+ config->memoryExport = memoryName;
}
}
error("-r and -pie may not be used together");
if (config->sharedMemory)
error("-r and --shared-memory may not be used together");
+ if (config->globalBase)
+ error("-r and --global-base may not by used together");
}
// To begin to prepare for Module Linking-style shared libraries, start
if (config->pie) {
warn("creating PIEs, with -pie, is not yet stable");
}
+
+ if (config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic) {
+ warn("dynamic imports are not yet stable "
+ "(--unresolved-symbols=import-dynamic)");
+ }
}
if (config->bsymbolic && !config->shared) {
warn("-Bsymbolic is only meaningful when combined with -shared");
}
+
+ if (config->globalBase && config->isPic) {
+ error("--global-base may not be used with -shared/-pie");
+ }
+}
+
+static const char *getReproduceOption(opt::InputArgList &args) {
+ if (auto *arg = args.getLastArg(OPT_reproduce))
+ return arg->getValue();
+ return getenv("LLD_REPRODUCE");
}
// Force Sym to be entered in the output. Used for -u or equivalent.
-static Symbol *handleUndefined(StringRef name) {
+static Symbol *handleUndefined(StringRef name, const char *option) {
Symbol *sym = symtab->find(name);
if (!sym)
return nullptr;
// eliminate it. Mark the symbol as "used" to prevent it.
sym->isUsedInRegularObj = true;
- if (auto *lazySym = dyn_cast<LazySymbol>(sym))
+ if (auto *lazySym = dyn_cast<LazySymbol>(sym)) {
lazySym->fetch();
+ if (!config->whyExtract.empty())
+ config->whyExtractRecords.emplace_back(option, sym->getFile(), *sym);
+ }
return sym;
}
if (auto *lazySym = dyn_cast<LazySymbol>(sym)) {
MemoryBufferRef mb = lazySym->getMemberBuffer();
- if (isBitcode(mb))
+ if (isBitcode(mb)) {
+ if (!config->whyExtract.empty())
+ config->whyExtractRecords.emplace_back("<libcall>", sym->getFile(),
+ *sym);
lazySym->fetch();
+ }
+ }
+}
+
+static void writeWhyExtract() {
+ if (config->whyExtract.empty())
+ return;
+
+ std::error_code ec;
+ raw_fd_ostream os(config->whyExtract, ec, sys::fs::OF_None);
+ if (ec) {
+ error("cannot open --why-extract= file " + config->whyExtract + ": " +
+ ec.message());
+ return;
+ }
+
+ os << "reference\textracted\tsymbol\n";
+ for (auto &entry : config->whyExtractRecords) {
+ os << std::get<0>(entry) << '\t' << toString(std::get<1>(entry)) << '\t'
+ << toString(std::get<2>(entry)) << '\n';
+ }
+}
+
+// Equivalent of demote demoteSharedAndLazySymbols() in the ELF linker
+static void demoteLazySymbols() {
+ for (Symbol *sym : symtab->symbols()) {
+ if (auto* s = dyn_cast<LazySymbol>(sym)) {
+ if (s->signature) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "demoting lazy func: " << s->getName() << "\n");
+ replaceSymbol<UndefinedFunction>(s, s->getName(), std::nullopt,
+ std::nullopt, WASM_SYMBOL_BINDING_WEAK,
+ s->getFile(), s->signature);
+ }
+ }
}
}
static UndefinedGlobal *
createUndefinedGlobal(StringRef name, llvm::wasm::WasmGlobalType *type) {
auto *sym = cast<UndefinedGlobal>(symtab->addUndefinedGlobal(
- name, None, None, WASM_SYMBOL_UNDEFINED, nullptr, type));
+ name, std::nullopt, std::nullopt, WASM_SYMBOL_UNDEFINED, nullptr, type));
config->allowUndefinedSymbols.insert(sym->getName());
sym->isUsedInRegularObj = true;
return sym;
static InputGlobal *createGlobal(StringRef name, bool isMutable) {
llvm::wasm::WasmGlobal wasmGlobal;
- bool is64 = config->is64.getValueOr(false);
+ bool is64 = config->is64.value_or(false);
wasmGlobal.Type = {uint8_t(is64 ? WASM_TYPE_I64 : WASM_TYPE_I32), isMutable};
wasmGlobal.InitExpr = intConst(0, is64);
wasmGlobal.SymbolName = name;
"__wasm_call_ctors", WASM_SYMBOL_VISIBILITY_HIDDEN,
make<SyntheticFunction>(nullSignature, "__wasm_call_ctors"));
- bool is64 = config->is64.getValueOr(false);
+ bool is64 = config->is64.value_or(false);
if (config->isPic) {
WasmSym::stackPointer =
- createUndefinedGlobal("__stack_pointer", config->is64.getValueOr(false)
+ createUndefinedGlobal("__stack_pointer", config->is64.value_or(false)
? &mutableGlobalTypeI64
: &mutableGlobalTypeI32);
// For PIC code, we import two global variables (__memory_base and
// __table_base) from the environment and use these as the offset at
// which to load our static data and function table.
// See:
- // https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md
+ // https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md
auto *globalType = is64 ? &globalTypeI64 : &globalTypeI32;
WasmSym::memoryBase = createUndefinedGlobal("__memory_base", globalType);
WasmSym::tableBase = createUndefinedGlobal("__table_base", globalType);
WasmSym::stackPointer->markLive();
}
- if (config->sharedMemory && !config->relocatable) {
+ if (config->sharedMemory) {
WasmSym::tlsBase = createGlobalVariable("__tls_base", true);
WasmSym::tlsSize = createGlobalVariable("__tls_size", false);
WasmSym::tlsAlign = createGlobalVariable("__tls_align", false);
is64 ? i64ArgSignature : i32ArgSignature,
"__wasm_init_tls"));
}
+
+ if (config->isPic ||
+ config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic) {
+ // For PIC code, or when dynamically importing addresses, we create
+ // synthetic functions that apply relocations. These get called from
+ // __wasm_call_ctors before the user-level constructors.
+ WasmSym::applyDataRelocs = symtab->addSyntheticFunction(
+ "__wasm_apply_data_relocs",
+ WASM_SYMBOL_VISIBILITY_DEFAULT | WASM_SYMBOL_EXPORTED,
+ make<SyntheticFunction>(nullSignature, "__wasm_apply_data_relocs"));
+ }
}
static void createOptionalSymbols() {
WasmSym::dataEnd = symtab->addOptionalDataSymbol("__data_end");
if (!config->isPic) {
+ WasmSym::stackLow = symtab->addOptionalDataSymbol("__stack_low");
+ WasmSym::stackHigh = symtab->addOptionalDataSymbol("__stack_high");
WasmSym::globalBase = symtab->addOptionalDataSymbol("__global_base");
WasmSym::heapBase = symtab->addOptionalDataSymbol("__heap_base");
+ WasmSym::heapEnd = symtab->addOptionalDataSymbol("__heap_end");
WasmSym::definedMemoryBase = symtab->addOptionalDataSymbol("__memory_base");
WasmSym::definedTableBase = symtab->addOptionalDataSymbol("__table_base");
- if (config->is64.getValueOr(false))
+ if (config->is64.value_or(false))
WasmSym::definedTableBase32 =
symtab->addOptionalDataSymbol("__table_base32");
}
// For non-shared memory programs we still need to define __tls_base since we
// allow object files built with TLS to be linked into single threaded
- // programs, and such object files can contains refernced to this symbol.
+ // programs, and such object files can contain references to this symbol.
//
// However, in this case __tls_base is immutable and points directly to the
// start of the `.tdata` static segment.
WasmSym::tlsBase = createOptionalGlobal("__tls_base", false);
}
+static void processStubLibraries() {
+ log("-- processStubLibraries");
+ for (auto &stub_file : symtab->stubFiles) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "processing stub file: " << stub_file->getName() << "\n");
+ for (auto [name, deps]: stub_file->symbolDependencies) {
+ auto* sym = symtab->find(name);
+ if (!sym || !sym->isUndefined() || !sym->isUsedInRegularObj ||
+ sym->forceImport) {
+ LLVM_DEBUG(llvm::dbgs() << "stub not in needed: " << name << "\n");
+ continue;
+ }
+ // The first stub library to define a given symbol sets this and
+ // definitions in later stub libraries are ignored.
+ sym->forceImport = true;
+ if (sym->traced)
+ message(toString(stub_file) + ": importing " + name);
+ else
+ LLVM_DEBUG(llvm::dbgs()
+ << toString(stub_file) << ": importing " << name << "\n");
+ for (const auto dep : deps) {
+ auto* needed = symtab->find(dep);
+ if (!needed) {
+ error(toString(stub_file) + ": undefined symbol: " + dep +
+ ". Required by " + toString(*sym));
+ } else if (needed->isUndefined()) {
+ error(toString(stub_file) +
+ ": undefined symbol: " + toString(*needed) +
+ ". Required by " + toString(*sym));
+ } else {
+ LLVM_DEBUG(llvm::dbgs()
+ << "force export: " << toString(*needed) << "\n");
+ needed->forceExport = true;
+ needed->isUsedInRegularObj = true;
+ if (auto *lazy = dyn_cast<LazySymbol>(needed)) {
+ lazy->fetch();
+ if (!config->whyExtract.empty())
+ config->whyExtractRecords.emplace_back(stub_file->getName(),
+ sym->getFile(), *sym);
+ }
+ }
+ }
+ }
+ }
+ log("-- done processStubLibraries");
+}
+
// Reconstructs command line arguments so that so that you can re-run
// the same command with the same inputs. This is for --reproduce.
static std::string createResponseFile(const opt::InputArgList &args) {
};
static Symbol *addUndefined(StringRef name) {
- return symtab->addUndefinedFunction(name, None, None, WASM_SYMBOL_UNDEFINED,
- nullptr, nullptr, false);
+ return symtab->addUndefinedFunction(name, std::nullopt, std::nullopt,
+ WASM_SYMBOL_UNDEFINED, nullptr, nullptr,
+ false);
}
// Handles -wrap option.
if (!sym)
continue;
- Symbol *real = addUndefined(saver.save("__real_" + name));
- Symbol *wrap = addUndefined(saver.save("__wrap_" + name));
+ Symbol *real = addUndefined(saver().save("__real_" + name));
+ Symbol *wrap = addUndefined(saver().save("__wrap_" + name));
v.push_back({sym, real, wrap});
// We want to tell LTO not to inline symbols to be overwritten
});
}
+static bool isKnownZFlag(StringRef s) {
+ // For now, we only support a very limited set of -z flags
+ return s.startswith("stack-size=");
+}
+
+// Report a warning for an unknown -z option.
+static void checkZOptions(opt::InputArgList &args) {
+ for (auto *arg : args.filtered(OPT_z))
+ if (!isKnownZFlag(arg->getValue()))
+ warn("unknown -z value: " + StringRef(arg->getValue()));
+}
+
void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
WasmOptTable parser;
opt::InputArgList args = parser.parse(argsArr.slice(1));
+ // Interpret these flags early because error()/warn() depend on them.
+ errorHandler().errorLimit = args::getInteger(args, OPT_error_limit, 20);
+ errorHandler().fatalWarnings =
+ args.hasFlag(OPT_fatal_warnings, OPT_no_fatal_warnings, false);
+ checkZOptions(args);
+
// Handle --help
if (args.hasArg(OPT_help)) {
parser.printHelp(lld::outs(),
}
// Handle --reproduce
- if (auto *arg = args.getLastArg(OPT_reproduce)) {
- StringRef path = arg->getValue();
+ if (const char *path = getReproduceOption(args)) {
Expected<std::unique_ptr<TarWriter>> errOrWriter =
TarWriter::create(path, path::stem(path));
if (errOrWriter) {
cl::ResetAllOptionOccurrences();
cl::ParseCommandLineOptions(v.size(), v.data());
- errorHandler().errorLimit = args::getInteger(args, OPT_error_limit, 20);
-
readConfigs(args);
+ setConfigs();
createFiles(args);
if (errorCount())
return;
- setConfigs();
checkOptions(args);
if (errorCount())
return;
// Handle the `--undefined <sym>` options.
for (auto *arg : args.filtered(OPT_undefined))
- handleUndefined(arg->getValue());
+ handleUndefined(arg->getValue(), "<internal>");
// Handle the `--export <sym>` options
// This works like --undefined but also exports the symbol if its found
for (auto &iter : config->exportedSymbols)
- handleUndefined(iter.first());
+ handleUndefined(iter.first(), "--export");
Symbol *entrySym = nullptr;
if (!config->relocatable && !config->entry.empty()) {
- entrySym = handleUndefined(config->entry);
+ entrySym = handleUndefined(config->entry, "--entry");
if (entrySym && entrySym->isDefined())
entrySym->forceExport = true;
else
!WasmSym::callCtors->isUsedInRegularObj &&
WasmSym::callCtors->getName() != config->entry &&
!config->exportedSymbols.count(WasmSym::callCtors->getName())) {
- if (Symbol *callDtors = handleUndefined("__wasm_call_dtors")) {
+ if (Symbol *callDtors =
+ handleUndefined("__wasm_call_dtors", "<internal>")) {
if (auto *callDtorsFunc = dyn_cast<DefinedFunction>(callDtors)) {
if (callDtorsFunc->signature &&
(!callDtorsFunc->signature->Params.empty() ||
}
}
- createOptionalSymbols();
-
if (errorCount())
return;
if (errorCount())
return;
+ writeWhyExtract();
+
// Do link-time optimization if given files are LLVM bitcode files.
// This compiles bitcode files into real object files.
- symtab->addCombinedLTOObject();
+ symtab->compileBitcodeFiles();
if (errorCount())
return;
+ processStubLibraries();
+
+ createOptionalSymbols();
+
// Resolve any variant symbols that were created due to signature
// mismatchs.
symtab->handleSymbolVariants();
// collection.
splitSections();
+ // Any remaining lazy symbols should be demoted to Undefined
+ demoteLazySymbols();
+
// Do size optimizations: garbage collection
markLive();
}
std::string toString(const wasm::InputChunk *c) {
- return (toString(c->file) + ":(" + c->getName() + ")").str();
+ return (toString(c->file) + ":(" + c->name + ")").str();
}
namespace wasm {
LLVM_DEBUG(dbgs() << " sym=" << file->getSymbols()[rel.Index]->getName());
LLVM_DEBUG(dbgs() << " addend=" << rel.Addend << " index=" << rel.Index
<< " offset=" << rel.Offset << "\n");
- auto value = file->calcNewValue(rel, tombstone, this);
+ // TODO(sbc): Check that the value is within the range of the
+ // relocation type below. Most likely we must error out here
+ // if its not with range.
+ uint64_t value = file->calcNewValue(rel, tombstone, this);
switch (rel.Type) {
case R_WASM_TYPE_INDEX_LEB:
case R_WASM_TAG_INDEX_LEB:
case R_WASM_MEMORY_ADDR_LEB:
case R_WASM_TABLE_NUMBER_LEB:
- encodeULEB128(value, loc, 5);
+ encodeULEB128(static_cast<uint32_t>(value), loc, 5);
break;
case R_WASM_MEMORY_ADDR_LEB64:
encodeULEB128(value, loc, 10);
}
void InputFunction::setFunctionIndex(uint32_t index) {
- LLVM_DEBUG(dbgs() << "InputFunction::setFunctionIndex: " << getName()
- << " -> " << index << "\n");
+ LLVM_DEBUG(dbgs() << "InputFunction::setFunctionIndex: " << name << " -> "
+ << index << "\n");
assert(!hasFunctionIndex());
functionIndex = index;
}
void InputFunction::setTableIndex(uint32_t index) {
- LLVM_DEBUG(dbgs() << "InputFunction::setTableIndex: " << getName() << " -> "
+ LLVM_DEBUG(dbgs() << "InputFunction::setTableIndex: " << name << " -> "
<< index << "\n");
assert(!hasTableIndex());
tableIndex = index;
if (!file || !config->compressRelocations)
return;
- LLVM_DEBUG(dbgs() << "calculateSize: " << getName() << "\n");
+ LLVM_DEBUG(dbgs() << "calculateSize: " << name << "\n");
const uint8_t *secStart = file->codeSection->Content.data();
const uint8_t *funcStart = secStart + getInputSectionOffset();
decodeULEB128(funcStart, &count);
funcStart += count;
- LLVM_DEBUG(dbgs() << "write func: " << getName() << "\n");
+ LLVM_DEBUG(dbgs() << "write func: " << name << "\n");
buf += encodeULEB128(compressedFuncSize, buf);
const uint8_t *lastRelocEnd = funcStart;
for (const WasmRelocation &rel : relocations) {
uint64_t InputChunk::getChunkOffset(uint64_t offset) const {
if (const auto *ms = dyn_cast<MergeInputChunk>(this)) {
- LLVM_DEBUG(dbgs() << "getChunkOffset(merged): " << getName() << "\n");
+ LLVM_DEBUG(dbgs() << "getChunkOffset(merged): " << name << "\n");
LLVM_DEBUG(dbgs() << "offset: " << offset << "\n");
LLVM_DEBUG(dbgs() << "parentOffset: " << ms->getParentOffset(offset)
<< "\n");
}
// Generate code to apply relocations to the data section at runtime.
-// This is only called when generating shared libaries (PIC) where address are
+// This is only called when generating shared libraries (PIC) where address are
// not known at static link time.
void InputChunk::generateRelocationCode(raw_ostream &os) const {
- LLVM_DEBUG(dbgs() << "generating runtime relocations: " << getName()
+ LLVM_DEBUG(dbgs() << "generating runtime relocations: " << name
<< " count=" << relocations.size() << "\n");
- bool is64 = config->is64.getValueOr(false);
+ bool is64 = config->is64.value_or(false);
unsigned opcode_ptr_const = is64 ? WASM_OPCODE_I64_CONST
: WASM_OPCODE_I32_CONST;
unsigned opcode_ptr_add = is64 ? WASM_OPCODE_I64_ADD
for (const WasmRelocation &rel : relocations) {
uint64_t offset = getVA(rel.Offset) - getInputSectionOffset();
+ Symbol *sym = file->getSymbol(rel);
+ if (!config->isPic && sym->isDefined())
+ continue;
+
LLVM_DEBUG(dbgs() << "gen reloc: type=" << relocTypeToString(rel.Type)
<< " addend=" << rel.Addend << " index=" << rel.Index
<< " output offset=" << offset << "\n");
- // Get __memory_base
- writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
- writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base");
-
- // Add the offset of the relocation
+ // Calculate the address at which to apply the relocations
writeU8(os, opcode_ptr_const, "CONST");
writeSleb128(os, offset, "offset");
- writeU8(os, opcode_ptr_add, "ADD");
+ // In PIC mode we need to add the __memory_base
+ if (config->isPic) {
+ writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
+ writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base");
+ writeU8(os, opcode_ptr_add, "ADD");
+ }
+
+ // Now figure out what we want to store at this location
bool is64 = relocIs64(rel.Type);
unsigned opcode_reloc_const =
is64 ? WASM_OPCODE_I64_CONST : WASM_OPCODE_I32_CONST;
unsigned opcode_reloc_store =
is64 ? WASM_OPCODE_I64_STORE : WASM_OPCODE_I32_STORE;
- Symbol *sym = file->getSymbol(rel);
- // Now figure out what we want to store
if (sym->hasGOTIndex()) {
writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
writeUleb128(os, sym->getGOTIndex(), "global index");
writeU8(os, opcode_reloc_add, "ADD");
}
} else {
+ assert(config->isPic);
const GlobalSymbol* baseSymbol = WasmSym::memoryBase;
if (rel.Type == R_WASM_TABLE_INDEX_I32 ||
rel.Type == R_WASM_TABLE_INDEX_I64)
#include "llvm/ADT/CachedHashString.h"
#include "llvm/MC/StringTableBuilder.h"
#include "llvm/Object/Wasm.h"
+#include <optional>
namespace lld {
namespace wasm {
StringRef name;
StringRef debugName;
- StringRef getName() const { return name; }
- StringRef getDebugName() const { return debugName; }
Kind kind() const { return (Kind)sectionKind; }
uint32_t getSize() const;
void writeRelocations(llvm::raw_ostream &os) const;
void generateRelocationCode(raw_ostream &os) const;
- bool isTLS() const {
- // Older object files don't include WASM_SEG_FLAG_TLS and instead
- // relied on the naming convention.
- return flags & llvm::wasm::WASM_SEG_FLAG_TLS || name.startswith(".tdata") ||
- name.startswith(".tbss");
- }
+ bool isTLS() const { return flags & llvm::wasm::WASM_SEG_FLAG_TLS; }
ObjFile *file;
OutputSection *outputSec = nullptr;
public:
SyntheticMergedChunk(StringRef name, uint32_t alignment, uint32_t flags)
: InputChunk(nullptr, InputChunk::MergedChunk, name, alignment, flags),
- builder(llvm::StringTableBuilder::RAW, 1ULL << alignment) {}
+ builder(llvm::StringTableBuilder::RAW, llvm::Align(1ULL << alignment)) {
+ }
static bool classof(const InputChunk *c) {
return c->kind() == InputChunk::MergedChunk;
public:
InputFunction(const WasmSignature &s, const WasmFunction *func, ObjFile *f)
: InputChunk(f, InputChunk::Function, func->SymbolName), signature(s),
- function(func), exportName(func && func->ExportName.hasValue()
- ? (*func->ExportName).str()
- : llvm::Optional<std::string>()) {
+ function(func),
+ exportName(func && func->ExportName ? (*func->ExportName).str()
+ : std::optional<std::string>()) {
inputSectionOffset = function->CodeSectionOffset;
rawData =
file->codeSection->Content.slice(inputSectionOffset, function->Size);
c->kind() == InputChunk::SyntheticFunction;
}
- llvm::Optional<StringRef> getExportName() const {
- return exportName.hasValue() ? llvm::Optional<StringRef>(*exportName)
- : llvm::Optional<StringRef>();
+ std::optional<StringRef> getExportName() const {
+ return exportName ? std::optional<StringRef>(*exportName)
+ : std::optional<StringRef>();
}
void setExportName(std::string exportName) { this->exportName = exportName; }
uint32_t getFunctionInputOffset() const { return getInputSectionOffset(); }
uint32_t getFunctionCodeOffset() const { return function->CodeOffset; }
- uint32_t getFunctionIndex() const { return functionIndex.getValue(); }
- bool hasFunctionIndex() const { return functionIndex.hasValue(); }
+ uint32_t getFunctionIndex() const { return *functionIndex; }
+ bool hasFunctionIndex() const { return functionIndex.has_value(); }
void setFunctionIndex(uint32_t index);
- uint32_t getTableIndex() const { return tableIndex.getValue(); }
- bool hasTableIndex() const { return tableIndex.hasValue(); }
+ uint32_t getTableIndex() const { return *tableIndex; }
+ bool hasTableIndex() const { return tableIndex.has_value(); }
void setTableIndex(uint32_t index);
void writeCompressed(uint8_t *buf) const;
const WasmFunction *function;
protected:
- llvm::Optional<std::string> exportName;
- llvm::Optional<uint32_t> functionIndex;
- llvm::Optional<uint32_t> tableIndex;
+ std::optional<std::string> exportName;
+ std::optional<uint32_t> functionIndex;
+ std::optional<uint32_t> tableIndex;
uint32_t compressedFuncSize = 0;
uint32_t compressedSize = 0;
};
#include "WriterUtils.h"
#include "lld/Common/LLVM.h"
#include "llvm/Object/Wasm.h"
+#include <optional>
namespace lld {
namespace wasm {
public:
StringRef getName() const { return name; }
- uint32_t getAssignedIndex() const { return assignedIndex.getValue(); }
- bool hasAssignedIndex() const { return assignedIndex.hasValue(); }
+ uint32_t getAssignedIndex() const { return *assignedIndex; }
+ bool hasAssignedIndex() const { return assignedIndex.has_value(); }
void assignIndex(uint32_t index) {
assert(!hasAssignedIndex());
assignedIndex = index;
protected:
StringRef name;
- llvm::Optional<uint32_t> assignedIndex;
+ std::optional<uint32_t> assignedIndex;
};
inline WasmInitExpr intConst(uint64_t value, bool is64) {
WasmInitExpr ie;
+ ie.Extended = false;
if (is64) {
- ie.Opcode = llvm::wasm::WASM_OPCODE_I64_CONST;
- ie.Value.Int64 = static_cast<int64_t>(value);
+ ie.Inst.Opcode = llvm::wasm::WASM_OPCODE_I64_CONST;
+ ie.Inst.Value.Int64 = static_cast<int64_t>(value);
} else {
- ie.Opcode = llvm::wasm::WASM_OPCODE_I32_CONST;
- ie.Value.Int32 = static_cast<int32_t>(value);
+ ie.Inst.Opcode = llvm::wasm::WASM_OPCODE_I32_CONST;
+ ie.Inst.Value.Int32 = static_cast<int32_t>(value);
}
return ie;
}
const WasmInitExpr &getInitExpr() const { return initExpr; }
void setPointerValue(uint64_t value) {
- initExpr = intConst(value, config->is64.getValueOr(false));
+ initExpr = intConst(value, config->is64.value_or(false));
}
private:
class InputTag : public InputElement {
public:
InputTag(const WasmSignature &s, const WasmTag &t, ObjFile *f)
- : InputElement(t.SymbolName, f), signature(s), type(t.Type) {}
-
- const WasmTagType &getType() const { return type; }
+ : InputElement(t.SymbolName, f), signature(s) {}
const WasmSignature &signature;
-
-private:
- WasmTagType type;
};
class InputTable : public InputElement {
#include "InputElement.h"
#include "OutputSegment.h"
#include "SymbolTable.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/Args.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Reproduce.h"
#include "llvm/Object/Binary.h"
#include "llvm/Object/Wasm.h"
+#include "llvm/Support/Path.h"
#include "llvm/Support/TarWriter.h"
#include "llvm/Support/raw_ostream.h"
+#include <optional>
#define DEBUG_TYPE "lld"
using namespace llvm;
using namespace llvm::object;
using namespace llvm::wasm;
+using namespace llvm::sys;
namespace lld {
void InputFile::checkArch(Triple::ArchType arch) const {
bool is64 = arch == Triple::wasm64;
- if (is64 && !config->is64.hasValue()) {
+ if (is64 && !config->is64) {
fatal(toString(this) +
": must specify -mwasm64 to process wasm64 object files");
- } else if (config->is64.getValueOr(false) != is64) {
+ } else if (config->is64.value_or(false) != is64) {
fatal(toString(this) +
": wasm32 object file can't be linked in wasm64 mode");
}
std::unique_ptr<llvm::TarWriter> tar;
-Optional<MemoryBufferRef> readFile(StringRef path) {
+std::optional<MemoryBufferRef> readFile(StringRef path) {
log("Loading: " + path);
auto mbOrErr = MemoryBuffer::getFile(path);
if (auto ec = mbOrErr.getError()) {
error("cannot open " + path + ": " + ec.message());
- return None;
+ return std::nullopt;
}
std::unique_ptr<MemoryBuffer> &mb = *mbOrErr;
MemoryBufferRef mbref = mb->getMemBufferRef();
return mbref;
}
-InputFile *createObjectFile(MemoryBufferRef mb, StringRef archiveName) {
+InputFile *createObjectFile(MemoryBufferRef mb, StringRef archiveName,
+ uint64_t offsetInArchive) {
file_magic magic = identify_magic(mb.getBuffer());
if (magic == file_magic::wasm_object) {
std::unique_ptr<Binary> bin =
}
if (magic == file_magic::bitcode)
- return make<BitcodeFile>(mb, archiveName);
+ return make<BitcodeFile>(mb, archiveName, offsetInArchive);
- fatal("unknown file type: " + mb.getBufferIdentifier());
-}
+ std::string name = mb.getBufferIdentifier().str();
+ if (!archiveName.empty()) {
+ name = archiveName.str() + "(" + name + ")";
+ }
-void ObjFile::dumpInfo() const {
- log("info for: " + toString(this) +
- "\n Symbols : " + Twine(symbols.size()) +
- "\n Function Imports : " + Twine(wasmObj->getNumImportedFunctions()) +
- "\n Global Imports : " + Twine(wasmObj->getNumImportedGlobals()) +
- "\n Tag Imports : " + Twine(wasmObj->getNumImportedTags()) +
- "\n Table Imports : " + Twine(wasmObj->getNumImportedTables()));
+ fatal("unknown file type: " + name);
}
// Relocations contain either symbol or type indices. This function takes a
// Relocations can contain addend for combined sections. This function takes a
// relocation and returns updated addend by offset in the output section.
-uint64_t ObjFile::calcNewAddend(const WasmRelocation &reloc) const {
+int64_t ObjFile::calcNewAddend(const WasmRelocation &reloc) const {
switch (reloc.Type) {
case R_WASM_MEMORY_ADDR_LEB:
case R_WASM_MEMORY_ADDR_LEB64:
case R_WASM_MEMORY_ADDR_REL_SLEB64:
case R_WASM_MEMORY_ADDR_I32:
case R_WASM_MEMORY_ADDR_I64:
+ case R_WASM_MEMORY_ADDR_TLS_SLEB:
+ case R_WASM_MEMORY_ADDR_TLS_SLEB64:
case R_WASM_MEMORY_ADDR_LOCREL_I32: {
if (isa<UndefinedData>(sym) || sym->isUndefWeak())
return 0;
auto D = cast<DefinedData>(sym);
- // Treat non-TLS relocation against symbols that live in the TLS segment
- // like TLS relocations. This beaviour exists to support older object
- // files created before we introduced TLS relocations.
- // TODO(sbc): Remove this legacy behaviour one day. This will break
- // backward compat with old object files built with `-fPIC`.
- if (D->segment && D->segment->outputSeg->isTLS())
- return D->getOutputSegmentOffset() + reloc.Addend;
-
uint64_t value = D->getVA() + reloc.Addend;
if (reloc.Type == R_WASM_MEMORY_ADDR_LOCREL_I32) {
const auto *segment = cast<InputSegment>(chunk);
}
return value;
}
- case R_WASM_MEMORY_ADDR_TLS_SLEB:
- case R_WASM_MEMORY_ADDR_TLS_SLEB64:
- if (isa<UndefinedData>(sym) || sym->isUndefWeak())
- return 0;
- // TLS relocations are relative to the start of the TLS output segment
- return cast<DefinedData>(sym)->getOutputSegmentOffset() + reloc.Addend;
case R_WASM_TYPE_INDEX_LEB:
return typeMap[reloc.Index];
case R_WASM_FUNCTION_INDEX_LEB:
return getTagSymbol(reloc.Index)->getTagIndex();
case R_WASM_FUNCTION_OFFSET_I32:
case R_WASM_FUNCTION_OFFSET_I64: {
+ if (isa<UndefinedFunction>(sym)) {
+ return tombstone ? tombstone : reloc.Addend;
+ }
auto *f = cast<DefinedFunction>(sym);
return f->function->getOffset(f->function->getFunctionCodeOffset() +
reloc.Addend);
LLVM_DEBUG(dbgs() << "Synthesizing symbol for table import: " << info->Name
<< "\n");
const WasmGlobalType *globalType = nullptr;
- const WasmTagType *tagType = nullptr;
const WasmSignature *signature = nullptr;
- auto *wasmSym = make<WasmSymbol>(*info, globalType, &tableImport->Table,
- tagType, signature);
+ auto *wasmSym =
+ make<WasmSymbol>(*info, globalType, &tableImport->Table, signature);
Symbol *sym = createUndefined(*wasmSym, false);
// We're only sure it's a TableSymbol if the createUndefined succeeded.
if (errorCount())
tableEntries.resize(totalFunctions);
for (const WasmElemSegment &seg : wasmObj->elements()) {
int64_t offset;
- if (seg.Offset.Opcode == WASM_OPCODE_I32_CONST)
- offset = seg.Offset.Value.Int32;
- else if (seg.Offset.Opcode == WASM_OPCODE_I64_CONST)
- offset = seg.Offset.Value.Int64;
+ if (seg.Offset.Extended)
+ fatal(toString(this) + ": extended init exprs not supported");
+ else if (seg.Offset.Inst.Opcode == WASM_OPCODE_I32_CONST)
+ offset = seg.Offset.Inst.Value.Int32;
+ else if (seg.Offset.Inst.Opcode == WASM_OPCODE_I64_CONST)
+ offset = seg.Offset.Inst.Value.Int64;
else
fatal(toString(this) + ": invalid table elements");
for (size_t index = 0; index < seg.Functions.size(); index++) {
// Populate `Segments`.
for (const WasmSegment &s : wasmObj->dataSegments()) {
InputChunk *seg;
- if (shouldMerge(s)) {
+ if (shouldMerge(s))
seg = make<MergeInputChunk>(s, this);
- } else
+ else
seg = make<InputSegment>(s, this);
seg->discarded = isExcludedByComdat(seg);
-
+ // Older object files did not include WASM_SEG_FLAG_TLS and instead
+ // relied on the naming convention. To maintain compat with such objects
+ // we still imply the TLS flag based on the name of the segment.
+ if (!seg->isTLS() &&
+ (seg->name.startswith(".tdata") || seg->name.startswith(".tbss")))
+ seg->flags |= WASM_SEG_FLAG_TLS;
segments.emplace_back(seg);
}
setRelocs(segments, dataSection);
// Populate `Functions`.
ArrayRef<WasmFunction> funcs = wasmObj->functions();
- ArrayRef<uint32_t> funcTypes = wasmObj->functionTypes();
ArrayRef<WasmSignature> types = wasmObj->types();
functions.reserve(funcs.size());
- for (size_t i = 0, e = funcs.size(); i != e; ++i) {
- auto* func = make<InputFunction>(types[funcTypes[i]], &funcs[i], this);
+ for (auto &f : funcs) {
+ auto *func = make<InputFunction>(types[f.SigIndex], &f, this);
func->discarded = isExcludedByComdat(func);
functions.emplace_back(func);
}
// Populate `Tags`.
for (const WasmTag &t : wasmObj->tags())
- tags.emplace_back(make<InputTag>(types[t.Type.SigIndex], t, this));
+ tags.emplace_back(make<InputTag>(types[t.SigIndex], t, this));
// Populate `Symbols` based on the symbols in the object.
symbols.reserve(wasmObj->getNumberOfSymbols());
addLegacyIndirectFunctionTableIfNeeded(tableSymbolCount);
}
-bool ObjFile::isExcludedByComdat(InputChunk *chunk) const {
+bool ObjFile::isExcludedByComdat(const InputChunk *chunk) const {
uint32_t c = chunk->getComdat();
if (c == UINT32_MAX)
return false;
InputChunk *seg = segments[sym.Info.DataRef.Segment];
auto offset = sym.Info.DataRef.Offset;
auto size = sym.Info.DataRef.Size;
+ // Support older (e.g. llvm 13) object files that pre-date the per-symbol
+ // TLS flag, and symbols were assumed to be TLS by being defined in a TLS
+ // segment.
+ if (!(flags & WASM_SYMBOL_TLS) && seg->isTLS())
+ flags |= WASM_SYMBOL_TLS;
if (sym.isBindingLocal())
return make<DefinedData>(name, flags, this, seg, offset, size);
if (seg->discarded)
return symtab->addUndefinedTable(name, sym.Info.ImportName,
sym.Info.ImportModule, flags, this,
sym.TableType);
+ case WASM_SYMBOL_TYPE_TAG:
+ if (sym.isBindingLocal())
+ return make<UndefinedTag>(name, sym.Info.ImportName,
+ sym.Info.ImportModule, flags, this,
+ sym.Signature);
+ return symtab->addUndefinedTag(name, sym.Info.ImportName,
+ sym.Info.ImportModule, flags, this,
+ sym.Signature);
case WASM_SYMBOL_TYPE_SECTION:
llvm_unreachable("section symbols cannot be undefined");
}
llvm_unreachable("unknown symbol kind");
}
+
+StringRef strip(StringRef s) {
+ while (s.starts_with(" ")) {
+ s = s.drop_front();
+ }
+ while (s.ends_with(" ")) {
+ s = s.drop_back();
+ }
+ return s;
+}
+
+void StubFile::parse() {
+ bool first = true;
+
+ SmallVector<StringRef> lines;
+ mb.getBuffer().split(lines, '\n');
+ for (StringRef line : lines) {
+ line = line.trim();
+
+ // File must begin with #STUB
+ if (first) {
+ assert(line == "#STUB");
+ first = false;
+ }
+
+ // Lines starting with # are considered comments
+ if (line.startswith("#"))
+ continue;
+
+ StringRef sym;
+ StringRef rest;
+ std::tie(sym, rest) = line.split(':');
+ sym = strip(sym);
+ rest = strip(rest);
+
+ symbolDependencies[sym] = {};
+
+ while (rest.size()) {
+ StringRef dep;
+ std::tie(dep, rest) = rest.split(',');
+ dep = strip(dep);
+ symbolDependencies[sym].push_back(dep);
+ }
+ }
+}
+
void ArchiveFile::parse() {
// Parse a MemoryBufferRef as an archive file.
LLVM_DEBUG(dbgs() << "Parsing library: " << toString(this) << "\n");
++count;
}
LLVM_DEBUG(dbgs() << "Read " << count << " symbols\n");
+ (void) count;
}
void ArchiveFile::addMember(const Archive::Symbol *sym) {
"could not get the buffer for the member defining symbol " +
sym->getName());
- InputFile *obj = createObjectFile(mb, getName());
+ InputFile *obj = createObjectFile(mb, getName(), c.getChildOffset());
symtab->addFile(obj);
}
static Symbol *createBitcodeSymbol(const std::vector<bool> &keptComdats,
const lto::InputFile::Symbol &objSym,
BitcodeFile &f) {
- StringRef name = saver.save(objSym.getName());
+ StringRef name = saver().save(objSym.getName());
uint32_t flags = objSym.isWeak() ? WASM_SYMBOL_BINDING_WEAK : 0;
flags |= mapVisibility(objSym.getVisibility());
if (objSym.isUndefined() || excludedByComdat) {
flags |= WASM_SYMBOL_UNDEFINED;
if (objSym.isExecutable())
- return symtab->addUndefinedFunction(name, None, None, flags, &f, nullptr,
- true);
+ return symtab->addUndefinedFunction(name, std::nullopt, std::nullopt,
+ flags, &f, nullptr, true);
return symtab->addUndefinedData(name, flags, &f);
}
return symtab->addDefinedData(name, flags, &f, nullptr, 0, 0);
}
+BitcodeFile::BitcodeFile(MemoryBufferRef m, StringRef archiveName,
+ uint64_t offsetInArchive)
+ : InputFile(BitcodeKind, m) {
+ this->archiveName = std::string(archiveName);
+
+ std::string path = mb.getBufferIdentifier().str();
+
+ // ThinLTO assumes that all MemoryBufferRefs given to it have a unique
+ // name. If two archives define two members with the same name, this
+ // causes a collision which result in only one of the objects being taken
+ // into consideration at LTO time (which very likely causes undefined
+ // symbols later in the link stage). So we append file offset to make
+ // filename unique.
+ StringRef name = archiveName.empty()
+ ? saver().save(path)
+ : saver().save(archiveName + "(" + path::filename(path) +
+ " at " + utostr(offsetInArchive) + ")");
+ MemoryBufferRef mbref(mb.getBuffer(), name);
+
+ obj = check(lto::InputFile::create(mbref));
+
+ // If this isn't part of an archive, it's eagerly linked, so mark it live.
+ if (archiveName.empty())
+ markLive();
+}
+
bool BitcodeFile::doneLTO = false;
void BitcodeFile::parse() {
return;
}
- obj = check(lto::InputFile::create(MemoryBufferRef(
- mb.getBuffer(), saver.save(archiveName + mb.getBufferIdentifier()))));
Triple t(obj->getTargetTriple());
if (!t.isWasm()) {
error(toString(this) + ": machine type must be wasm32 or wasm64");
#include "llvm/Object/Archive.h"
#include "llvm/Object/Wasm.h"
#include "llvm/Support/MemoryBuffer.h"
+#include <optional>
#include <vector>
namespace llvm {
SharedKind,
ArchiveKind,
BitcodeKind,
+ StubKind,
};
virtual ~InputFile() {}
// Returns the underlying wasm file.
const WasmObjectFile *getWasmObj() const { return wasmObj.get(); }
- void dumpInfo() const;
-
uint32_t calcNewIndex(const WasmRelocation &reloc) const;
uint64_t calcNewValue(const WasmRelocation &reloc, uint64_t tombstone,
const InputChunk *chunk) const;
- uint64_t calcNewAddend(const WasmRelocation &reloc) const;
+ int64_t calcNewAddend(const WasmRelocation &reloc) const;
Symbol *getSymbol(const WasmRelocation &reloc) const {
return symbols[reloc.Index];
};
Symbol *createDefined(const WasmSymbol &sym);
Symbol *createUndefined(const WasmSymbol &sym, bool isCalledDirectly);
- bool isExcludedByComdat(InputChunk *chunk) const;
+ bool isExcludedByComdat(const InputChunk *chunk) const;
void addLegacyIndirectFunctionTableIfNeeded(uint32_t tableSymbolCount);
std::unique_ptr<WasmObjectFile> wasmObj;
// .bc file
class BitcodeFile : public InputFile {
public:
- explicit BitcodeFile(MemoryBufferRef m, StringRef archiveName)
- : InputFile(BitcodeKind, m) {
- this->archiveName = std::string(archiveName);
-
- // If this isn't part of an archive, it's eagerly linked, so mark it live.
- if (archiveName.empty())
- markLive();
- }
+ BitcodeFile(MemoryBufferRef m, StringRef archiveName,
+ uint64_t offsetInArchive);
static bool classof(const InputFile *f) { return f->kind() == BitcodeKind; }
void parse();
static bool doneLTO;
};
+// Stub libray (See docs/WebAssembly.rst)
+class StubFile : public InputFile {
+public:
+ explicit StubFile(MemoryBufferRef m) : InputFile(StubKind, m) {}
+
+ static bool classof(const InputFile *f) { return f->kind() == StubKind; }
+
+ void parse();
+
+ llvm::DenseMap<StringRef, std::vector<StringRef>> symbolDependencies;
+};
+
inline bool isBitcode(MemoryBufferRef mb) {
return identify_magic(mb.getBuffer()) == llvm::file_magic::bitcode;
}
// Will report a fatal() error if the input buffer is not a valid bitcode
// or wasm object file.
-InputFile *createObjectFile(MemoryBufferRef mb, StringRef archiveName = "");
+InputFile *createObjectFile(MemoryBufferRef mb, StringRef archiveName = "",
+ uint64_t offsetInArchive = 0);
// Opens a given file.
-llvm::Optional<MemoryBufferRef> readFile(StringRef path);
+std::optional<MemoryBufferRef> readFile(StringRef path);
} // namespace wasm
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/IR/DiagnosticPrinter.h"
-#include "llvm/LTO/Caching.h"
#include "llvm/LTO/Config.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Object/SymbolicFile.h"
+#include "llvm/Support/Caching.h"
#include "llvm/Support/CodeGen.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
c.OptLevel = config->ltoo;
c.MAttrs = getMAttrs();
c.CGOptLevel = args::getCGOptLevel(config->ltoo);
- c.UseNewPM = config->ltoNewPassManager;
c.DebugPassManager = config->ltoDebugPassManager;
if (config->relocatable)
- c.RelocModel = None;
+ c.RelocModel = std::nullopt;
else if (config->isPic)
c.RelocModel = Reloc::PIC_;
else
static void undefine(Symbol *s) {
if (auto f = dyn_cast<DefinedFunction>(s))
- replaceSymbol<UndefinedFunction>(f, f->getName(), None, None, 0,
- f->getFile(), f->signature);
+ replaceSymbol<UndefinedFunction>(f, f->getName(), std::nullopt,
+ std::nullopt, 0, f->getFile(),
+ f->signature);
else if (isa<DefinedData>(s))
replaceSymbol<UndefinedData>(s, s->getName(), 0, s->getFile());
else
// The --thinlto-cache-dir option specifies the path to a directory in which
// to cache native object files for ThinLTO incremental builds. If a path was
// specified, configure LTO to use it as the cache directory.
- lto::NativeObjectCache cache;
+ FileCache cache;
if (!config->thinLTOCacheDir.empty())
- cache = check(
- lto::localCache(config->thinLTOCacheDir,
- [&](size_t task, std::unique_ptr<MemoryBuffer> mb) {
- files[task] = std::move(mb);
- }));
+ cache = check(localCache("ThinLTO", "Thin", config->thinLTOCacheDir,
+ [&](size_t task, const Twine &moduleName,
+ std::unique_ptr<MemoryBuffer> mb) {
+ files[task] = std::move(mb);
+ }));
checkError(ltoObj->run(
- [&](size_t task) {
- return std::make_unique<lto::NativeObjectStream>(
+ [&](size_t task, const Twine &moduleName) {
+ return std::make_unique<CachedFileStream>(
std::make_unique<raw_svector_ostream>(buf[task]));
},
cache));
if (!config->thinLTOCacheDir.empty())
- pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy);
+ pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy, files);
std::vector<StringRef> ret;
for (unsigned i = 0; i != maxTasks; ++i) {
#include "SyntheticSections.h"
#include "lld/Common/Strings.h"
#include "llvm/ADT/MapVector.h"
-#include "llvm/ADT/SetVector.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/raw_ostream.h"
static DenseMap<Symbol *, std::string>
getSymbolStrings(ArrayRef<Symbol *> syms) {
std::vector<std::string> str(syms.size());
- parallelForEachN(0, syms.size(), [&](size_t i) {
+ parallelFor(0, syms.size(), [&](size_t i) {
raw_string_ostream os(str[i]);
auto *chunk = syms[i]->getChunk();
if (chunk == nullptr)
enqueue(symtab->find(config->entry));
// We need to preserve any no-strip or exported symbol
- for (Symbol *sym : symtab->getSymbols())
+ for (Symbol *sym : symtab->symbols())
if (sym->isNoStrip() || sym->isExported())
enqueue(sym);
include "llvm/Option/OptParser.td"
+// Convenience classes for long options which only accept two dashes. For lld
+// specific or newer long options, we prefer two dashes to avoid collision with
+// short options. For many others, we have to accept both forms to be compatible
+// with GNU ld.
+class FF<string name> : Flag<["--"], name>;
+class JJ<string name>: Joined<["--"], name>;
+
+multiclass EEq<string name, string help> {
+ def NAME: Separate<["--"], name>;
+ def NAME # _eq: Joined<["--"], name # "=">, Alias<!cast<Separate>(NAME)>,
+ HelpText<help>;
+}
+
+multiclass BB<string name, string help1, string help2> {
+ def NAME: Flag<["--"], name>, HelpText<help1>;
+ def no_ # NAME: Flag<["--"], "no-" # name>, HelpText<help2>;
+}
+
// For options whose names are multiple letters, either one dash or
// two can precede the option name except those that start with 'o'.
class F<string name>: Flag<["--", "-"], name>;
def no_ # NAME: Flag<["--", "-"], "no-" # name>, HelpText<help2>;
}
-multiclass BB<string name, string help1, string help2> {
- def NAME: Flag<["--"], name>, HelpText<help1>;
- def no_ # NAME: Flag<["--"], "no-" # name>, HelpText<help2>;
-}
-
// The following flags are shared with the ELF linker
def Bsymbolic: F<"Bsymbolic">, HelpText<"Bind defined symbols locally">;
+def Bdynamic: F<"Bdynamic">, HelpText<"Link against shared libraries (default)">;
+
+def Bstatic: F<"Bstatic">, HelpText<"Do not link against shared libraries">;
+
defm color_diagnostics: B<"color-diagnostics",
"Alias for --color-diagnostics=always",
"Alias for --color-diagnostics=never">;
def entry: S<"entry">, MetaVarName<"<entry>">,
HelpText<"Name of entry point symbol">;
-def error_limit: J<"error-limit=">,
- HelpText<"Maximum number of errors to emit before stopping (0 = no limit)">;
+defm error_limit:
+ EEq<"error-limit", "Maximum number of errors to emit before stopping (0 = no limit)">;
def fatal_warnings: F<"fatal-warnings">,
HelpText<"Treat warnings as errors">;
"Enable garbage collection of unused sections",
"Disable garbage collection of unused sections">;
-defm merge_data_segments: B<"merge-data-segments",
+defm merge_data_segments: BB<"merge-data-segments",
"Enable merging data segments",
"Disable merging data segments">;
def help: F<"help">, HelpText<"Print option help">;
-def l: JoinedOrSeparate<["-"], "l">, MetaVarName<"<libName>">,
+def library: JoinedOrSeparate<["-"], "l">, MetaVarName<"<libName>">,
HelpText<"Root name of library to use">;
-def L: JoinedOrSeparate<["-"], "L">, MetaVarName<"<dir>">,
+def library_path: JoinedOrSeparate<["-"], "L">, MetaVarName<"<dir>">,
HelpText<"Add a directory to the library search path">;
def m: JoinedOrSeparate<["-"], "m">, HelpText<"Set target emulation">;
-def mllvm: S<"mllvm">, HelpText<"Options to pass to LLVM">;
+defm mllvm: Eq<"mllvm", "Additional arguments to forward to LLVM's option processing">;
defm Map: Eq<"Map", "Print a link map to the specified file">;
def relocatable: F<"relocatable">, HelpText<"Create relocatable object file">;
-defm reproduce: Eq<"reproduce", "Dump linker invocation and input files for debugging">;
+defm reproduce: EEq<"reproduce", "Dump linker invocation and input files for debugging">;
defm rsp_quoting: Eq<"rsp-quoting", "Quoting style for response files">,
MetaVarName<"[posix,windows]">;
// The follow flags are unique to wasm
def allow_undefined: F<"allow-undefined">,
- HelpText<"Allow undefined symbols in linked binary. This options is equivelant "
- "to --import-undefined and --unresolved-symbols=ignore-all">;
+ HelpText<"Allow undefined symbols in linked binary. "
+ "This options is equivalent to --import-undefined "
+ "and --unresolved-symbols=ignore-all">;
def import_undefined: F<"import-undefined">,
HelpText<"Turn undefined symbols into imports where possible">;
defm export_if_defined: Eq<"export-if-defined",
"Force a symbol to be exported, if it is defined in the input">;
-def export_all: F<"export-all">,
+def export_all: FF<"export-all">,
HelpText<"Export all symbols (normally combined with --no-gc-sections)">;
-def export_table: F<"export-table">,
+def export_table: FF<"export-table">,
HelpText<"Export function table to the environment">;
-def growable_table: F<"growable-table">,
+def growable_table: FF<"growable-table">,
HelpText<"Remove maximum size from function table, allowing table to grow">;
-def global_base: J<"global-base=">,
+def global_base: JJ<"global-base=">,
HelpText<"Where to start to place global data">;
-def import_memory: F<"import-memory">,
- HelpText<"Import memory from the environment">;
+def import_memory: FF<"import-memory">,
+ HelpText<"Import the module's memory from the default module of \"env\" with the name \"memory\".">;
+def import_memory_with_name: JJ<"import-memory=">,
+ HelpText<"Import the module's memory from the passed module with the passed name.">,
+ MetaVarName<"<module>,<name>">;
+
+def export_memory: FF<"export-memory">,
+ HelpText<"Export the module's memory with the default name of \"memory\"">;
+def export_memory_with_name: JJ<"export-memory=">,
+ HelpText<"Export the module's memory with the passed name">;
-def shared_memory: F<"shared-memory">,
+def shared_memory: FF<"shared-memory">,
HelpText<"Use shared linear memory">;
-def import_table: F<"import-table">,
+def import_table: FF<"import-table">,
HelpText<"Import function table from the environment">;
-def initial_memory: J<"initial-memory=">,
+def initial_memory: JJ<"initial-memory=">,
HelpText<"Initial size of the linear memory">;
-def max_memory: J<"max-memory=">,
+def max_memory: JJ<"max-memory=">,
HelpText<"Maximum size of the linear memory">;
-def no_entry: F<"no-entry">,
+def no_entry: FF<"no-entry">,
HelpText<"Do not output any entry point">;
-def stack_first: F<"stack-first">,
+def stack_first: FF<"stack-first">,
HelpText<"Place stack at start of linear memory rather than after data">;
defm whole_archive: B<"whole-archive",
"Force load of all members in a static library",
"Do not force load of all members in a static library (default)">;
-defm check_features: B<"check-features",
+def why_extract: JJ<"why-extract=">, HelpText<"Print to a file about why archive members are extracted">;
+
+defm check_features: BB<"check-features",
"Check feature compatibility of linked objects (default)",
"Ignore feature compatibility of linked objects">;
def features: CommaJoined<["--", "-"], "features=">,
HelpText<"Comma-separated used features, inferred from input objects by default.">;
+def extra_features: CommaJoined<["--", "-"], "extra-features=">,
+ HelpText<"Comma-separated list of features to add to the default set of features inferred from input objects.">;
+
// Aliases
def: JoinedOrSeparate<["-"], "e">, Alias<entry>;
def: J<"entry=">, Alias<entry>;
+def: F<"call_shared">, Alias<Bdynamic>, HelpText<"Alias for --Bdynamic">;
+def: F<"dy">, Alias<Bdynamic>, HelpText<"Alias for --Bdynamic">;
+def: F<"dn">, Alias<Bstatic>, HelpText<"Alias for --Bstatic">;
+def: F<"non_shared">, Alias<Bstatic>, HelpText<"Alias for --Bstatic">;
+def: F<"static">, Alias<Bstatic>, HelpText<"Alias for --Bstatic">;
def: Flag<["-"], "E">, Alias<export_dynamic>, HelpText<"Alias for --export-dynamic">;
def: Flag<["-"], "i">, Alias<initial_memory>;
+def: Separate<["--", "-"], "library">, Alias<library>;
+def: Joined<["--", "-"], "library=">, Alias<library>;
+def: Separate<["--", "-"], "library-path">, Alias<library_path>;
+def: Joined<["--", "-"], "library-path=">, Alias<library_path>;
def: Flag<["-"], "M">, Alias<print_map>, HelpText<"Alias for --print-map">;
def: Flag<["-"], "r">, Alias<relocatable>;
def: Flag<["-"], "s">, Alias<strip_all>, HelpText<"Alias for --strip-all">;
def: JoinedOrSeparate<["-"], "u">, Alias<undefined>;
// LTO-related options.
-def lto_O: J<"lto-O">, MetaVarName<"<opt-level>">,
+def lto_O: JJ<"lto-O">, MetaVarName<"<opt-level>">,
HelpText<"Optimization level for LTO">;
-def lto_partitions: J<"lto-partitions=">,
+def lto_partitions: JJ<"lto-partitions=">,
HelpText<"Number of LTO codegen partitions">;
def disable_verify: F<"disable-verify">;
def save_temps: F<"save-temps">, HelpText<"Save intermediate LTO compilation results">;
-def thinlto_cache_dir: J<"thinlto-cache-dir=">,
+def thinlto_cache_dir: JJ<"thinlto-cache-dir=">,
HelpText<"Path to ThinLTO cached object file directory">;
-defm thinlto_cache_policy: Eq<"thinlto-cache-policy", "Pruning policy for the ThinLTO cache">;
-def thinlto_jobs: J<"thinlto-jobs=">,
+defm thinlto_cache_policy: EEq<"thinlto-cache-policy", "Pruning policy for the ThinLTO cache">;
+def thinlto_jobs: JJ<"thinlto-jobs=">,
HelpText<"Number of ThinLTO jobs. Default to --threads=">;
-defm lto_legacy_pass_manager: BB<"lto-legacy-pass-manager", "Use legacy pass manager", "Use new pass manager">;
-def lto_debug_pass_manager: F<"lto-debug-pass-manager">,
+def no_lto_legacy_pass_manager: FF<"no-lto-legacy-pass-manager">,
+ HelpText<"Use new pass manager">;
+def lto_debug_pass_manager: FF<"lto-debug-pass-manager">,
HelpText<"Debug new pass manager">;
// Experimental PIC mode.
-def experimental_pic: F<"experimental-pic">,
+def experimental_pic: FF<"experimental-pic">,
HelpText<"Enable Experimental PIC">;
}
namespace wasm {
-static StringRef sectionTypeToString(uint32_t sectionType) {
- switch (sectionType) {
- case WASM_SEC_CUSTOM:
- return "CUSTOM";
- case WASM_SEC_TYPE:
- return "TYPE";
- case WASM_SEC_IMPORT:
- return "IMPORT";
- case WASM_SEC_FUNCTION:
- return "FUNCTION";
- case WASM_SEC_TABLE:
- return "TABLE";
- case WASM_SEC_MEMORY:
- return "MEMORY";
- case WASM_SEC_GLOBAL:
- return "GLOBAL";
- case WASM_SEC_TAG:
- return "TAG";
- case WASM_SEC_EXPORT:
- return "EXPORT";
- case WASM_SEC_START:
- return "START";
- case WASM_SEC_ELEM:
- return "ELEM";
- case WASM_SEC_CODE:
- return "CODE";
- case WASM_SEC_DATA:
- return "DATA";
- case WASM_SEC_DATACOUNT:
- return "DATACOUNT";
- default:
- fatal("invalid section type");
- }
-}
-
StringRef OutputSection::getSectionName() const {
return sectionTypeToString(type);
}
}
void CodeSection::writeTo(uint8_t *buf) {
- log("writing " + toString(*this));
- log(" size=" + Twine(getSize()));
+ log("writing " + toString(*this) + " offset=" + Twine(offset) +
+ " size=" + Twine(getSize()));
log(" headersize=" + Twine(header.size()));
log(" codeheadersize=" + Twine(codeSectionHeader.size()));
buf += offset;
void DataSection::finalizeContents() {
raw_string_ostream os(dataSectionHeader);
- unsigned segmentCount =
- std::count_if(segments.begin(), segments.end(),
- [](OutputSegment *segment) { return !segment->isBss; });
-
+ unsigned segmentCount = llvm::count_if(segments, [](OutputSegment *segment) {
+ return segment->requiredInBinary();
+ });
#ifndef NDEBUG
- unsigned activeCount = std::count_if(
- segments.begin(), segments.end(), [](OutputSegment *segment) {
- return (segment->initFlags & WASM_DATA_SEGMENT_IS_PASSIVE) == 0;
- });
+ unsigned activeCount = llvm::count_if(segments, [](OutputSegment *segment) {
+ return (segment->initFlags & WASM_DATA_SEGMENT_IS_PASSIVE) == 0;
+ });
#endif
- assert((config->sharedMemory || !config->isPic || activeCount <= 1) &&
+ assert((config->sharedMemory || !config->isPic || config->extendedConst ||
+ activeCount <= 1) &&
"output segments should have been combined by now");
writeUleb128(os, segmentCount, "data segment count");
os.flush();
bodySize = dataSectionHeader.size();
+ bool is64 = config->is64.value_or(false);
for (OutputSegment *segment : segments) {
- if (segment->isBss)
+ if (!segment->requiredInBinary())
continue;
raw_string_ostream os(segment->header);
writeUleb128(os, segment->initFlags, "init flags");
if (segment->initFlags & WASM_DATA_SEGMENT_HAS_MEMINDEX)
writeUleb128(os, 0, "memory index");
if ((segment->initFlags & WASM_DATA_SEGMENT_IS_PASSIVE) == 0) {
- WasmInitExpr initExpr;
- if (config->isPic) {
- initExpr.Opcode = WASM_OPCODE_GLOBAL_GET;
- initExpr.Value.Global = WasmSym::memoryBase->getGlobalIndex();
+ if (config->isPic && config->extendedConst) {
+ writeU8(os, WASM_OPCODE_GLOBAL_GET, "global get");
+ writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(),
+ "literal (global index)");
+ if (segment->startVA) {
+ writePtrConst(os, segment->startVA, is64, "offset");
+ writeU8(os, is64 ? WASM_OPCODE_I64_ADD : WASM_OPCODE_I32_ADD, "add");
+ }
+ writeU8(os, WASM_OPCODE_END, "opcode:end");
} else {
- initExpr = intConst(segment->startVA, config->is64.getValueOr(false));
+ WasmInitExpr initExpr;
+ initExpr.Extended = false;
+ if (config->isPic) {
+ assert(segment->startVA == 0);
+ initExpr.Inst.Opcode = WASM_OPCODE_GLOBAL_GET;
+ initExpr.Inst.Value.Global = WasmSym::memoryBase->getGlobalIndex();
+ } else {
+ initExpr = intConst(segment->startVA, is64);
+ }
+ writeInitExpr(os, initExpr);
}
- writeInitExpr(os, initExpr);
}
writeUleb128(os, segment->size, "segment size");
os.flush();
}
void DataSection::writeTo(uint8_t *buf) {
- log("writing " + toString(*this) + " size=" + Twine(getSize()) +
- " body=" + Twine(bodySize));
+ log("writing " + toString(*this) + " offset=" + Twine(offset) +
+ " size=" + Twine(getSize()) + " body=" + Twine(bodySize));
buf += offset;
// Write section header
memcpy(buf, dataSectionHeader.data(), dataSectionHeader.size());
for (const OutputSegment *segment : segments) {
- if (segment->isBss)
+ if (!segment->requiredInBinary())
continue;
// Write data segment header
uint8_t *segStart = buf + segment->sectionOffset;
bool DataSection::isNeeded() const {
for (const OutputSegment *seg : segments)
- if (!seg->isBss)
+ if (seg->requiredInBinary())
return true;
return false;
}
}
void CustomSection::writeTo(uint8_t *buf) {
- log("writing " + toString(*this) + " size=" + Twine(getSize()) +
- " chunks=" + Twine(inputSections.size()));
+ log("writing " + toString(*this) + " offset=" + Twine(offset) +
+ " size=" + Twine(getSize()) + " chunks=" + Twine(inputSections.size()));
assert(offset);
buf += offset;
virtual ~OutputSection() = default;
StringRef getSectionName() const;
- void setOffset(size_t newOffset) {
- log("setOffset: " + toString(*this) + ": " + Twine(newOffset));
- offset = newOffset;
- }
+ void setOffset(size_t newOffset) { offset = newOffset; }
void createHeader(size_t bodySize);
virtual bool isNeeded() const { return true; }
virtual size_t getSize() const = 0;
alignment = std::max(alignment, inSeg->alignment);
inputSegments.push_back(inSeg);
size = llvm::alignTo(size, 1ULL << inSeg->alignment);
- LLVM_DEBUG(dbgs() << "addInputSegment: " << inSeg->getName()
- << " oname=" << name << " size=" << inSeg->getSize()
+ LLVM_DEBUG(dbgs() << "addInputSegment: " << inSeg->name << " oname=" << name
+ << " size=" << inSeg->getSize()
<< " align=" << inSeg->alignment << " at:" << size << "\n");
inSeg->outputSeg = this;
inSeg->outputSegmentOffset = size;
size = 0;
for (InputChunk *seg : inputSegments) {
size = llvm::alignTo(size, 1ULL << seg->alignment);
- LLVM_DEBUG(llvm::dbgs() << "outputSegmentOffset set: " << seg->getName()
+ LLVM_DEBUG(llvm::dbgs() << "outputSegmentOffset set: " << seg->name
<< " -> " << size << "\n");
seg->outputSegmentOffset = size;
size += seg->getSize();
void addInputSegment(InputChunk *inSeg);
void finalizeInputSegments();
+ // In most circumstances BSS segments don't need to be written
+ // to the output binary. However if the memory is imported, and
+ // we can't use memory.fill during startup (due to lack of bulk
+ // memory feature) then we include BSS segments verbatim.
+ bool requiredInBinary() const { return !isBss || config->emitBssSegments; }
bool isTLS() const { return name == ".tdata"; }
namespace wasm {
static bool requiresGOTAccess(const Symbol *sym) {
- if (!config->isPic)
+ if (!config->isPic &&
+ config->unresolvedSymbols != UnresolvedPolicy::ImportDynamic)
return false;
if (sym->isHidden() || sym->isLocal())
return false;
}
static bool allowUndefined(const Symbol* sym) {
- // Undefined functions and globals with explicit import name are allowed to be
- // undefined at link time.
- if (auto *f = dyn_cast<UndefinedFunction>(sym))
- if (f->importName || config->importUndefined)
- return true;
- if (auto *g = dyn_cast<UndefinedGlobal>(sym))
- if (g->importName)
- return true;
- if (auto *g = dyn_cast<UndefinedGlobal>(sym))
- if (g->importName)
- return true;
+ // Symbols that are explicitly imported are always allowed to be undefined at
+ // link time.
+ if (sym->isImported())
+ return true;
+ if (isa<UndefinedFunction>(sym) && config->importUndefined)
+ return true;
+
return config->allowUndefinedSymbols.count(sym->getName()) != 0;
}
}
}
break;
+ case UnresolvedPolicy::ImportDynamic:
+ break;
}
}
}
break;
case R_WASM_MEMORY_ADDR_TLS_SLEB:
case R_WASM_MEMORY_ADDR_TLS_SLEB64:
+ if (!sym->isDefined()) {
+ error(toString(file) + ": relocation " + relocTypeToString(reloc.Type) +
+ " cannot be used against an undefined symbol `" + toString(*sym) +
+ "`");
+ }
// In single-threaded builds TLS is lowered away and TLS data can be
// merged with normal data and allowing TLS relocation in non-TLS
// segments.
if (config->sharedMemory) {
+ if (!sym->isTLS()) {
+ error(toString(file) + ": relocation " +
+ relocTypeToString(reloc.Type) +
+ " cannot be used against non-TLS symbol `" + toString(*sym) +
+ "`");
+ }
if (auto *D = dyn_cast<DefinedData>(sym)) {
if (!D->segment->outputSeg->isTLS()) {
error(toString(file) + ": relocation " +
break;
}
- if (config->isPic) {
+ if (config->isPic ||
+ (sym->isUndefined() &&
+ config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic)) {
switch (reloc.Type) {
case R_WASM_TABLE_INDEX_SLEB:
case R_WASM_TABLE_INDEX_SLEB64:
// Certain relocation types can't be used when building PIC output,
// since they would require absolute symbol addresses at link time.
error(toString(file) + ": relocation " + relocTypeToString(reloc.Type) +
- " cannot be used against symbol " + toString(*sym) +
- "; recompile with -fPIC");
- break;
- case R_WASM_MEMORY_ADDR_TLS_SLEB:
- case R_WASM_MEMORY_ADDR_TLS_SLEB64:
- if (!sym->isDefined()) {
- error(toString(file) +
- ": TLS symbol is undefined, but TLS symbols cannot yet be "
- "imported: `" +
- toString(*sym) + "`");
- }
+ " cannot be used against symbol `" + toString(*sym) +
+ "`; recompile with -fPIC");
break;
case R_WASM_TABLE_INDEX_I32:
case R_WASM_TABLE_INDEX_I64:
case R_WASM_MEMORY_ADDR_I64:
// These relocation types are only present in the data section and
// will be converted into code by `generateRelocationCode`. This code
- // requires the symbols to have GOT entires.
+ // requires the symbols to have GOT entries.
if (requiresGOTAccess(sym))
addGOTEntry(sym);
break;
#include "InputChunks.h"
#include "InputElement.h"
#include "WriterUtils.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
-#include "llvm/ADT/SetVector.h"
+#include "lld/Common/CommonLinkerContext.h"
+#include <optional>
#define DEBUG_TYPE "lld"
return;
}
+ // stub file
+ if (auto *f = dyn_cast<StubFile>(file)) {
+ f->parse();
+ stubFiles.push_back(f);
+ return;
+ }
+
if (config->trace)
message(toString(file));
// using LLVM functions and replaces bitcode symbols with the results.
// Because all bitcode files that the program consists of are passed
// to the compiler at once, it can do whole-program optimization.
-void SymbolTable::addCombinedLTOObject() {
+void SymbolTable::compileBitcodeFiles() {
// Prevent further LTO objects being included
BitcodeFile::doneLTO = true;
sym->canInline = true;
sym->traced = trace;
sym->forceExport = false;
+ sym->referenced = !config->gcSections;
symVector.emplace_back(sym);
return {sym, true};
}
const WasmSignature *newSig) {
const WasmSignature *oldSig = existing->signature;
- // If either function is missing a signature (this happend for bitcode
+ // If either function is missing a signature (this happens for bitcode
// symbols) then assume they match. Any mismatch will be reported later
// when the LTO objects are added.
if (!newSig || !oldSig)
}
static void checkTagType(const Symbol *existing, const InputFile *file,
- const WasmTagType *newType,
const WasmSignature *newSig) {
const auto *existingTag = dyn_cast<TagSymbol>(existing);
if (!isa<TagSymbol>(existing)) {
return;
}
- const WasmTagType *oldType = cast<TagSymbol>(existing)->getTagType();
const WasmSignature *oldSig = existingTag->signature;
- if (newType->Attribute != oldType->Attribute)
- error("Tag type mismatch: " + existing->getName() + "\n>>> defined as " +
- toString(*oldType) + " in " + toString(existing->getFile()) +
- "\n>>> defined as " + toString(*newType) + " in " + toString(file));
if (*newSig != *oldSig)
warn("Tag signature mismatch: " + existing->getName() +
"\n>>> defined as " + toString(*oldSig) + " in " +
DefinedGlobal *SymbolTable::addOptionalGlobalSymbol(StringRef name,
InputGlobal *global) {
- LLVM_DEBUG(dbgs() << "addOptionalGlobalSymbol: " << name << " -> " << global
- << "\n");
Symbol *s = find(name);
if (!s || s->isDefined())
return nullptr;
+ LLVM_DEBUG(dbgs() << "addOptionalGlobalSymbol: " << name << " -> " << global
+ << "\n");
syntheticGlobals.emplace_back(global);
return replaceSymbol<DefinedGlobal>(s, name, WASM_SYMBOL_VISIBILITY_HIDDEN,
nullptr, global);
return s;
}
- checkTagType(s, file, &tag->getType(), &tag->signature);
+ checkTagType(s, file, &tag->signature);
if (shouldReplace(s, file, flags))
replaceSym();
// become available when the LTO object is read. In this case we silently
// replace the empty attributes with the valid ones.
template <typename T>
-static void setImportAttributes(T *existing, Optional<StringRef> importName,
- Optional<StringRef> importModule,
+static void setImportAttributes(T *existing,
+ std::optional<StringRef> importName,
+ std::optional<StringRef> importModule,
uint32_t flags, InputFile *file) {
if (importName) {
if (!existing->importName)
}
Symbol *SymbolTable::addUndefinedFunction(StringRef name,
- Optional<StringRef> importName,
- Optional<StringRef> importModule,
+ std::optional<StringRef> importName,
+ std::optional<StringRef> importModule,
uint32_t flags, InputFile *file,
const WasmSignature *sig,
bool isCalledDirectly) {
lazy->signature = sig;
} else {
lazy->fetch();
+ if (!config->whyExtract.empty())
+ config->whyExtractRecords.emplace_back(toString(file), s->getFile(),
+ *s);
}
} else {
auto existingFunction = dyn_cast<FunctionSymbol>(s);
else if (getFunctionVariant(s, sig, file, &s))
replaceSym();
}
- if (existingUndefined)
+ if (existingUndefined) {
setImportAttributes(existingUndefined, importName, importModule, flags,
file);
+ if (isCalledDirectly)
+ existingUndefined->isCalledDirectly = true;
+ }
}
return s;
}
Symbol *SymbolTable::addUndefinedGlobal(StringRef name,
- Optional<StringRef> importName,
- Optional<StringRef> importModule,
+ std::optional<StringRef> importName,
+ std::optional<StringRef> importModule,
uint32_t flags, InputFile *file,
const WasmGlobalType *type) {
LLVM_DEBUG(dbgs() << "addUndefinedGlobal: " << name << "\n");
}
Symbol *SymbolTable::addUndefinedTable(StringRef name,
- Optional<StringRef> importName,
- Optional<StringRef> importModule,
+ std::optional<StringRef> importName,
+ std::optional<StringRef> importModule,
uint32_t flags, InputFile *file,
const WasmTableType *type) {
LLVM_DEBUG(dbgs() << "addUndefinedTable: " << name << "\n");
return s;
}
+Symbol *SymbolTable::addUndefinedTag(StringRef name,
+ std::optional<StringRef> importName,
+ std::optional<StringRef> importModule,
+ uint32_t flags, InputFile *file,
+ const WasmSignature *sig) {
+ LLVM_DEBUG(dbgs() << "addUndefinedTag: " << name << "\n");
+ assert(flags & WASM_SYMBOL_UNDEFINED);
+
+ Symbol *s;
+ bool wasInserted;
+ std::tie(s, wasInserted) = insert(name, file);
+ if (s->traced)
+ printTraceSymbolUndefined(name, file);
+
+ if (wasInserted)
+ replaceSymbol<UndefinedTag>(s, name, importName, importModule, flags, file,
+ sig);
+ else if (auto *lazy = dyn_cast<LazySymbol>(s))
+ lazy->fetch();
+ else if (s->isDefined())
+ checkTagType(s, file, sig);
+ return s;
+}
+
TableSymbol *SymbolTable::createUndefinedIndirectFunctionTable(StringRef name) {
WasmLimits limits{0, 0, 0}; // Set by the writer.
WasmTableType *type = make<WasmTableType>();
}
LLVM_DEBUG(dbgs() << "replacing existing undefined\n");
+ const InputFile *oldFile = s->getFile();
file->addMember(sym);
+ if (!config->whyExtract.empty())
+ config->whyExtractRecords.emplace_back(toString(oldFile), s->getFile(), *s);
}
bool SymbolTable::addComdat(StringRef name) {
void SymbolTable::replaceWithUndefined(Symbol *sym) {
// Add a synthetic dummy for weak undefined functions. These dummies will
// be GC'd if not used as the target of any "call" instructions.
- StringRef debugName = saver.save("undefined_weak:" + toString(*sym));
+ StringRef debugName = saver().save("undefined_weak:" + toString(*sym));
replaceWithUnreachable(sym, *sym->getSignature(), debugName);
// Hide our dummy to prevent export.
sym->setHidden(true);
// will abort at runtime, so that relocations can still provided an operand to
// the call instruction that passes Wasm validation.
void SymbolTable::handleWeakUndefines() {
- for (Symbol *sym : getSymbols()) {
- if (sym->isUndefWeak()) {
+ for (Symbol *sym : symbols()) {
+ if (sym->isUndefWeak() && sym->isUsedInRegularObj) {
if (sym->getSignature()) {
replaceWithUndefined(sym);
} else {
if (symbol != defined) {
auto *f = cast<FunctionSymbol>(symbol);
reportFunctionSignatureMismatch(symName, f, defined, false);
- StringRef debugName = saver.save("signature_mismatch:" + toString(*f));
+ StringRef debugName =
+ saver().save("signature_mismatch:" + toString(*f));
replaceWithUnreachable(f, *f->signature, debugName);
}
}
#include "lld/Common/LLVM.h"
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseSet.h"
-#include "llvm/ADT/Optional.h"
#include "llvm/BinaryFormat/WasmTraits.h"
+#include <optional>
namespace lld {
namespace wasm {
// There is one add* function per symbol type.
class SymbolTable {
public:
+ ArrayRef<Symbol *> symbols() const { return symVector; }
+
void wrap(Symbol *sym, Symbol *real, Symbol *wrap);
void addFile(InputFile *file);
- void addCombinedLTOObject();
-
- ArrayRef<Symbol *> getSymbols() const { return symVector; }
+ void compileBitcodeFiles();
Symbol *find(StringRef name);
InputTable *t);
Symbol *addUndefinedFunction(StringRef name,
- llvm::Optional<StringRef> importName,
- llvm::Optional<StringRef> importModule,
+ std::optional<StringRef> importName,
+ std::optional<StringRef> importModule,
uint32_t flags, InputFile *file,
const WasmSignature *signature,
bool isCalledDirectly);
Symbol *addUndefinedData(StringRef name, uint32_t flags, InputFile *file);
Symbol *addUndefinedGlobal(StringRef name,
- llvm::Optional<StringRef> importName,
- llvm::Optional<StringRef> importModule,
+ std::optional<StringRef> importName,
+ std::optional<StringRef> importModule,
uint32_t flags, InputFile *file,
const WasmGlobalType *type);
- Symbol *addUndefinedTable(StringRef name,
- llvm::Optional<StringRef> importName,
- llvm::Optional<StringRef> importModule,
+ Symbol *addUndefinedTable(StringRef name, std::optional<StringRef> importName,
+ std::optional<StringRef> importModule,
uint32_t flags, InputFile *file,
const WasmTableType *type);
+ Symbol *addUndefinedTag(StringRef name, std::optional<StringRef> importName,
+ std::optional<StringRef> importModule, uint32_t flags,
+ InputFile *file, const WasmSignature *sig);
TableSymbol *resolveIndirectFunctionTable(bool required);
DefinedFunction *createUndefinedStub(const WasmSignature &sig);
std::vector<ObjFile *> objectFiles;
+ std::vector<StubFile *> stubFiles;
std::vector<SharedFile *> sharedFiles;
std::vector<BitcodeFile *> bitcodeFiles;
std::vector<InputFunction *> syntheticFunctions;
#include "OutputSegment.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
-#include "lld/Common/Strings.h"
+#include "llvm/Demangle/Demangle.h"
#define DEBUG_TYPE "lld"
using namespace llvm;
using namespace llvm::object;
using namespace llvm::wasm;
+using namespace lld::wasm;
namespace lld {
std::string toString(const wasm::Symbol &sym) {
if (name == "__main_argc_argv")
return "main";
if (wasm::config->demangle)
- return demangleItanium(name);
- return std::string(name);
+ return demangle(name.str());
+ return name.str();
}
std::string toString(wasm::Symbol::Kind kind) {
return "UndefinedGlobal";
case wasm::Symbol::UndefinedTableKind:
return "UndefinedTable";
+ case wasm::Symbol::UndefinedTagKind:
+ return "UndefinedTag";
case wasm::Symbol::LazyKind:
return "LazyKind";
case wasm::Symbol::SectionKind:
DefinedFunction *WasmSym::initMemory;
DefinedFunction *WasmSym::applyDataRelocs;
DefinedFunction *WasmSym::applyGlobalRelocs;
+DefinedFunction *WasmSym::applyGlobalTLSRelocs;
DefinedFunction *WasmSym::initTLS;
DefinedFunction *WasmSym::startFunction;
DefinedData *WasmSym::dsoHandle;
DefinedData *WasmSym::dataEnd;
DefinedData *WasmSym::globalBase;
DefinedData *WasmSym::heapBase;
+DefinedData *WasmSym::heapEnd;
DefinedData *WasmSym::initMemoryFlag;
GlobalSymbol *WasmSym::stackPointer;
+DefinedData *WasmSym::stackLow;
+DefinedData *WasmSym::stackHigh;
GlobalSymbol *WasmSym::tlsBase;
GlobalSymbol *WasmSym::tlsSize;
GlobalSymbol *WasmSym::tlsAlign;
const WasmSignature *Symbol::getSignature() const {
if (auto* f = dyn_cast<FunctionSymbol>(this))
return f->signature;
+ if (auto *t = dyn_cast<TagSymbol>(this))
+ return t->signature;
if (auto *l = dyn_cast<LazySymbol>(this))
return l->signature;
return nullptr;
void Symbol::markLive() {
assert(!isDiscarded());
referenced = true;
- if (file != NULL && isDefined())
+ if (file != nullptr && isDefined())
file->markLive();
if (auto *g = dyn_cast<DefinedGlobal>(this))
g->global->live = true;
void Symbol::setGOTIndex(uint32_t index) {
LLVM_DEBUG(dbgs() << "setGOTIndex " << name << " -> " << index << "\n");
assert(gotIndex == INVALID_INDEX);
- if (config->isPic) {
- // Any symbol that is assigned a GOT entry must be exported otherwise the
- // dynamic linker won't be able create the entry that contains it.
- forceExport = true;
- }
gotIndex = index;
}
return (flags & WASM_SYMBOL_VISIBILITY_MASK) == WASM_SYMBOL_VISIBILITY_HIDDEN;
}
+bool Symbol::isTLS() const { return flags & WASM_SYMBOL_TLS; }
+
void Symbol::setHidden(bool isHidden) {
LLVM_DEBUG(dbgs() << "setHidden: " << name << " -> " << isHidden << "\n");
flags &= ~WASM_SYMBOL_VISIBILITY_MASK;
flags |= WASM_SYMBOL_VISIBILITY_DEFAULT;
}
+bool Symbol::isImported() const {
+ return isUndefined() && (importName.has_value() || forceImport);
+}
+
bool Symbol::isExported() const {
if (!isDefined() || isLocal())
return false;
+ // Shared libraries must export all weakly defined symbols
+ // in case they contain the version that will be chosen by
+ // the dynamic linker.
+ if (config->shared && isLive() && isWeak() && !isHidden())
+ return true;
+
if (config->exportAll || (config->exportDynamic && !isHidden()))
return true;
}
uint32_t FunctionSymbol::getFunctionIndex() const {
- if (auto *f = dyn_cast<DefinedFunction>(this))
- return f->function->getFunctionIndex();
- if (const auto *u = dyn_cast<UndefinedFunction>(this)) {
- if (u->stubFunction) {
+ if (const auto *u = dyn_cast<UndefinedFunction>(this))
+ if (u->stubFunction)
return u->stubFunction->getFunctionIndex();
- }
- }
- assert(functionIndex != INVALID_INDEX);
- return functionIndex;
+ if (functionIndex != INVALID_INDEX)
+ return functionIndex;
+ auto *f = cast<DefinedFunction>(this);
+ return f->function->getFunctionIndex();
}
void FunctionSymbol::setFunctionIndex(uint32_t index) {
function ? &function->signature : nullptr),
function(function) {}
+uint32_t DefinedFunction::getExportedFunctionIndex() const {
+ return function->getFunctionIndex();
+}
+
uint64_t DefinedData::getVA() const {
LLVM_DEBUG(dbgs() << "getVA: " << getName() << "\n");
+ // In the shared memory case, TLS symbols are relative to the start of the TLS
+ // output segment (__tls_base). When building without shared memory, TLS
+ // symbols absolute, just like non-TLS.
+ if (isTLS() && config->sharedMemory)
+ return getOutputSegmentOffset() + value;
if (segment)
return segment->getVA(value);
return value;
DefinedTag::DefinedTag(StringRef name, uint32_t flags, InputFile *file,
InputTag *tag)
: TagSymbol(name, DefinedTagKind, flags, file,
- tag ? &tag->getType() : nullptr,
tag ? &tag->signature : nullptr),
tag(tag) {}
const char *defaultModule = "env";
const char *functionTableName = "__indirect_function_table";
+const char *memoryName = "memory";
} // namespace wasm
} // namespace lld
#include "Config.h"
#include "lld/Common/LLVM.h"
-#include "llvm/ADT/Optional.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/Wasm.h"
+#include <optional>
namespace lld {
namespace wasm {
// The name under which to import or export the wasm table.
extern const char *functionTableName;
+// The name under which to import or export the wasm memory.
+extern const char *memoryName;
+
using llvm::wasm::WasmSymbolType;
class InputFile;
UndefinedDataKind,
UndefinedGlobalKind,
UndefinedTableKind,
+ UndefinedTagKind,
LazyKind,
};
return symbolKind == UndefinedFunctionKind ||
symbolKind == UndefinedDataKind ||
symbolKind == UndefinedGlobalKind ||
- symbolKind == UndefinedTableKind;
+ symbolKind == UndefinedTableKind || symbolKind == UndefinedTagKind;
}
bool isLazy() const { return symbolKind == LazyKind; }
bool isLocal() const;
bool isWeak() const;
bool isHidden() const;
+ bool isTLS() const;
// Returns true if this symbol exists in a discarded (due to COMDAT) section
bool isDiscarded() const;
void setOutputSymbolIndex(uint32_t index);
WasmSymbolType getWasmType() const;
+ bool isImported() const;
bool isExported() const;
bool isExportedExplicit() const;
Symbol(StringRef name, Kind k, uint32_t flags, InputFile *f)
: name(name), file(f), symbolKind(k), referenced(!config->gcSections),
requiresGOT(false), isUsedInRegularObj(false), forceExport(false),
- canInline(false), traced(false), isStub(false), flags(flags) {}
+ forceImport(false), canInline(false), traced(false), isStub(false),
+ flags(flags) {}
StringRef name;
InputFile *file;
// are unreferenced except by other bitcode objects.
bool isUsedInRegularObj : 1;
- // True if ths symbol is explicitly marked for export (i.e. via the
+ // True if this symbol is explicitly marked for export (i.e. via the
// -e/--export command line flag)
bool forceExport : 1;
+ bool forceImport : 1;
+
// False if LTO shouldn't inline whatever this symbol points to. If a symbol
// is overwritten after LTO, LTO shouldn't inline the symbol because it
// doesn't know the final contents of the symbol.
uint32_t flags;
- llvm::Optional<StringRef> importName;
- llvm::Optional<StringRef> importModule;
+ std::optional<StringRef> importName;
+ std::optional<StringRef> importModule;
};
class FunctionSymbol : public Symbol {
return s->kind() == DefinedFunctionKind;
}
+ // Get the function index to be used when exporting. This only applies to
+ // defined functions and can be differ from the regular function index for
+ // weakly defined functions (that are imported and used via one index but
+ // defined and exported via another).
+ uint32_t getExportedFunctionIndex() const;
+
InputFunction *function;
};
class UndefinedFunction : public FunctionSymbol {
public:
- UndefinedFunction(StringRef name, llvm::Optional<StringRef> importName,
- llvm::Optional<StringRef> importModule, uint32_t flags,
+ UndefinedFunction(StringRef name, std::optional<StringRef> importName,
+ std::optional<StringRef> importModule, uint32_t flags,
InputFile *file = nullptr,
const WasmSignature *type = nullptr,
bool isCalledDirectly = true)
class UndefinedGlobal : public GlobalSymbol {
public:
- UndefinedGlobal(StringRef name, llvm::Optional<StringRef> importName,
- llvm::Optional<StringRef> importModule, uint32_t flags,
+ UndefinedGlobal(StringRef name, std::optional<StringRef> importName,
+ std::optional<StringRef> importModule, uint32_t flags,
InputFile *file = nullptr,
const WasmGlobalType *type = nullptr)
: GlobalSymbol(name, UndefinedGlobalKind, flags, file, type) {
class UndefinedTable : public TableSymbol {
public:
- UndefinedTable(StringRef name, llvm::Optional<StringRef> importName,
- llvm::Optional<StringRef> importModule, uint32_t flags,
+ UndefinedTable(StringRef name, std::optional<StringRef> importName,
+ std::optional<StringRef> importModule, uint32_t flags,
InputFile *file, const WasmTableType *type)
: TableSymbol(name, UndefinedTableKind, flags, file, type) {
this->importName = importName;
// and is named '__cpp_exception' for linking.
class TagSymbol : public Symbol {
public:
- static bool classof(const Symbol *s) { return s->kind() == DefinedTagKind; }
-
- const WasmTagType *getTagType() const { return tagType; }
+ static bool classof(const Symbol *s) {
+ return s->kind() == DefinedTagKind || s->kind() == UndefinedTagKind;
+ }
// Get/set the tag index
uint32_t getTagIndex() const;
protected:
TagSymbol(StringRef name, Kind k, uint32_t flags, InputFile *f,
- const WasmTagType *tagType, const WasmSignature *sig)
- : Symbol(name, k, flags, f), signature(sig), tagType(tagType) {}
+ const WasmSignature *sig)
+ : Symbol(name, k, flags, f), signature(sig) {}
- const WasmTagType *tagType;
uint32_t tagIndex = INVALID_INDEX;
};
InputTag *tag;
};
+class UndefinedTag : public TagSymbol {
+public:
+ UndefinedTag(StringRef name, std::optional<StringRef> importName,
+ std::optional<StringRef> importModule, uint32_t flags,
+ InputFile *file = nullptr, const WasmSignature *sig = nullptr)
+ : TagSymbol(name, UndefinedTagKind, flags, file, sig) {
+ this->importName = importName;
+ this->importModule = importModule;
+ }
+
+ static bool classof(const Symbol *s) { return s->kind() == UndefinedTagKind; }
+};
+
// LazySymbol represents a symbol that is not yet in the link, but we know where
// to find it if needed. If the resolver finds both Undefined and Lazy for the
// same name, it will ask the Lazy to load a file.
MemoryBufferRef getMemberBuffer();
// Lazy symbols can have a signature because they can replace an
- // UndefinedFunction which which case we need to be able to preserve the
+ // UndefinedFunction in which case we need to be able to preserve the
// signature.
// TODO(sbc): This repetition of the signature field is inelegant. Revisit
// the use of class hierarchy to represent symbol taxonomy.
// Symbol marking the start of the global section.
static DefinedData *globalBase;
- // __stack_pointer
- // Global that holds the address of the top of the explicit value stack in
- // linear memory.
+ // __stack_pointer/__stack_low/__stack_high
+ // Global that holds current value of stack pointer and data symbols marking
+ // the start and end of the stack region. stackPointer is initialized to
+ // stackHigh and grows downwards towards stackLow
static GlobalSymbol *stackPointer;
+ static DefinedData *stackLow;
+ static DefinedData *stackHigh;
// __tls_base
// Global that holds the address of the base of the current thread's
// Symbol marking the end of the data and bss.
static DefinedData *dataEnd;
- // __heap_base
- // Symbol marking the end of the data, bss and explicit stack. Any linear
- // memory following this address is not used by the linked code and can
- // therefore be used as a backing store for brk()/malloc() implementations.
+ // __heap_base/__heap_end
+ // Symbols marking the beginning and end of the "heap". It starts at the end
+ // of the data, bss and explicit stack, and extends to the end of the linear
+ // memory allocated by wasm-ld. This region of memory is not used by the
+ // linked code, so it may be used as a backing store for `sbrk` or `malloc`
+ // implementations.
static DefinedData *heapBase;
+ static DefinedData *heapEnd;
// __wasm_init_memory_flag
// Symbol whose contents are nonzero iff memory has already been initialized.
static DefinedFunction *applyDataRelocs;
// __wasm_apply_global_relocs
- // Function that applies relocations to data segment post-instantiation.
+ // Function that applies relocations to wasm globals post-instantiation.
// Unlike __wasm_apply_data_relocs this needs to run on every thread.
static DefinedFunction *applyGlobalRelocs;
+ // __wasm_apply_global_tls_relocs
+ // Like applyGlobalRelocs but for globals that hold TLS addresses. These
+ // must be delayed until __wasm_init_tls.
+ static DefinedFunction *applyGlobalTLSRelocs;
+
// __wasm_init_tls
// Function that allocates thread-local storage and initializes it.
static DefinedFunction *initTLS;
T *s2 = new (s) T(std::forward<ArgT>(arg)...);
s2->isUsedInRegularObj = symCopy.isUsedInRegularObj;
s2->forceExport = symCopy.forceExport;
+ s2->forceImport = symCopy.forceImport;
s2->canInline = symCopy.canInline;
s2->traced = symCopy.traced;
+ s2->referenced = symCopy.referenced;
// Print out a log message if --trace-symbol was specified.
// This is for debugging.
#include "OutputSegment.h"
#include "SymbolTable.h"
#include "llvm/Support/Path.h"
+#include <optional>
using namespace llvm;
using namespace llvm::wasm;
} // namespace
+bool DylinkSection::isNeeded() const {
+ return config->isPic ||
+ config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic ||
+ !symtab->sharedFiles.empty();
+}
+
void DylinkSection::writeBody() {
raw_ostream &os = bodyOutputStream;
- writeUleb128(os, memSize, "MemSize");
- writeUleb128(os, memAlign, "MemAlign");
- writeUleb128(os, out.elemSec->numEntries(), "TableSize");
- writeUleb128(os, 0, "TableAlign");
- writeUleb128(os, symtab->sharedFiles.size(), "Needed");
- for (auto *so : symtab->sharedFiles)
- writeStr(os, llvm::sys::path::filename(so->getName()), "so name");
+ {
+ SubSection sub(WASM_DYLINK_MEM_INFO);
+ writeUleb128(sub.os, memSize, "MemSize");
+ writeUleb128(sub.os, memAlign, "MemAlign");
+ writeUleb128(sub.os, out.elemSec->numEntries(), "TableSize");
+ writeUleb128(sub.os, 0, "TableAlign");
+ sub.writeTo(os);
+ }
+
+ if (symtab->sharedFiles.size()) {
+ SubSection sub(WASM_DYLINK_NEEDED);
+ writeUleb128(sub.os, symtab->sharedFiles.size(), "Needed");
+ for (auto *so : symtab->sharedFiles)
+ writeStr(sub.os, llvm::sys::path::filename(so->getName()), "so name");
+ sub.writeTo(os);
+ }
+
+ // Under certain circumstances we need to include extra information about our
+ // exports and/or imports to the dynamic linker.
+ // For exports we need to notify the linker when an export is TLS since the
+ // exported value is relative to __tls_base rather than __memory_base.
+ // For imports we need to notify the dynamic linker when an import is weak
+ // so that knows not to report an error for such symbols.
+ std::vector<const Symbol *> importInfo;
+ std::vector<const Symbol *> exportInfo;
+ for (const Symbol *sym : symtab->symbols()) {
+ if (sym->isLive()) {
+ if (sym->isExported() && sym->isTLS() && isa<DefinedData>(sym)) {
+ exportInfo.push_back(sym);
+ }
+ if (sym->isUndefWeak()) {
+ importInfo.push_back(sym);
+ }
+ }
+ }
+
+ if (!exportInfo.empty()) {
+ SubSection sub(WASM_DYLINK_EXPORT_INFO);
+ writeUleb128(sub.os, exportInfo.size(), "num exports");
+
+ for (const Symbol *sym : exportInfo) {
+ LLVM_DEBUG(llvm::dbgs() << "export info: " << toString(*sym) << "\n");
+ StringRef name = sym->getName();
+ if (auto *f = dyn_cast<DefinedFunction>(sym)) {
+ if (std::optional<StringRef> exportName =
+ f->function->getExportName()) {
+ name = *exportName;
+ }
+ }
+ writeStr(sub.os, name, "sym name");
+ writeUleb128(sub.os, sym->flags, "sym flags");
+ }
+
+ sub.writeTo(os);
+ }
+
+ if (!importInfo.empty()) {
+ SubSection sub(WASM_DYLINK_IMPORT_INFO);
+ writeUleb128(sub.os, importInfo.size(), "num imports");
+
+ for (const Symbol *sym : importInfo) {
+ LLVM_DEBUG(llvm::dbgs() << "imports info: " << toString(*sym) << "\n");
+ StringRef module = sym->importModule.value_or(defaultModule);
+ StringRef name = sym->importName.value_or(sym->getName());
+ writeStr(sub.os, module, "import module");
+ writeStr(sub.os, name, "import name");
+ writeUleb128(sub.os, sym->flags, "sym flags");
+ }
+
+ sub.writeTo(os);
+ }
}
uint32_t TypeSection::registerType(const WasmSignature &sig) {
uint32_t ImportSection::getNumImports() const {
assert(isSealed);
uint32_t numImports = importedSymbols.size() + gotSymbols.size();
- if (config->importMemory)
+ if (config->memoryImport.has_value())
++numImports;
return numImports;
}
return;
LLVM_DEBUG(dbgs() << "addGOTEntry: " << toString(*sym) << "\n");
sym->setGOTIndex(numImportedGlobals++);
+ if (config->isPic) {
+ // Any symbol that is assigned an normal GOT entry must be exported
+ // otherwise the dynamic linker won't be able create the entry that contains
+ // it.
+ sym->forceExport = true;
+ }
gotSymbols.push_back(sym);
}
void ImportSection::addImport(Symbol *sym) {
assert(!isSealed);
- StringRef module = sym->importModule.getValueOr(defaultModule);
- StringRef name = sym->importName.getValueOr(sym->getName());
+ StringRef module = sym->importModule.value_or(defaultModule);
+ StringRef name = sym->importName.value_or(sym->getName());
if (auto *f = dyn_cast<FunctionSymbol>(sym)) {
ImportKey<WasmSignature> key(*(f->getSignature()), module, name);
auto entry = importedFunctions.try_emplace(key, numImportedFunctions);
g->setGlobalIndex(entry.first->second);
}
} else if (auto *t = dyn_cast<TagSymbol>(sym)) {
- // NB: There's currently only one possible kind of tag, and no
- // `UndefinedTag`, so we don't bother de-duplicating tag imports.
- importedSymbols.emplace_back(sym);
- t->setTagIndex(numImportedTags++);
+ ImportKey<WasmSignature> key(*(t->getSignature()), module, name);
+ auto entry = importedTags.try_emplace(key, numImportedTags);
+ if (entry.second) {
+ importedSymbols.emplace_back(sym);
+ t->setTagIndex(numImportedTags++);
+ } else {
+ t->setTagIndex(entry.first->second);
+ }
} else {
assert(TableSymbol::classof(sym));
auto *table = cast<TableSymbol>(sym);
writeUleb128(os, getNumImports(), "import count");
- bool is64 = config->is64.getValueOr(false);
+ bool is64 = config->is64.value_or(false);
- if (config->importMemory) {
+ if (config->memoryImport) {
WasmImport import;
- import.Module = defaultModule;
- import.Field = "memory";
+ import.Module = config->memoryImport->first;
+ import.Field = config->memoryImport->second;
import.Kind = WASM_EXTERNAL_MEMORY;
import.Memory.Flags = 0;
import.Memory.Minimum = out.memorySec->numMemoryPages;
for (const Symbol *sym : importedSymbols) {
WasmImport import;
- import.Field = sym->importName.getValueOr(sym->getName());
- import.Module = sym->importModule.getValueOr(defaultModule);
+ import.Field = sym->importName.value_or(sym->getName());
+ import.Module = sym->importModule.value_or(defaultModule);
if (auto *functionSym = dyn_cast<FunctionSymbol>(sym)) {
import.Kind = WASM_EXTERNAL_FUNCTION;
import.Global = *globalSym->getGlobalType();
} else if (auto *tagSym = dyn_cast<TagSymbol>(sym)) {
import.Kind = WASM_EXTERNAL_TAG;
- import.Tag.Attribute = tagSym->getTagType()->Attribute;
- import.Tag.SigIndex = out.typeSec->lookupType(*tagSym->signature);
+ import.SigIndex = out.typeSec->lookupType(*tagSym->signature);
} else {
auto *tableSym = cast<TableSymbol>(sym);
import.Kind = WASM_EXTERNAL_TABLE;
flags |= WASM_LIMITS_FLAG_HAS_MAX;
if (config->sharedMemory)
flags |= WASM_LIMITS_FLAG_IS_SHARED;
- if (config->is64.getValueOr(false))
+ if (config->is64.value_or(false))
flags |= WASM_LIMITS_FLAG_IS_64;
writeUleb128(os, flags, "memory limits flags");
writeUleb128(os, numMemoryPages, "initial pages");
writeUleb128(os, inputTags.size(), "tag count");
for (InputTag *t : inputTags) {
- WasmTagType type = t->getType();
- type.SigIndex = out.typeSec->lookupType(t->signature);
- writeTagType(os, type);
+ writeUleb128(os, 0, "tag attribute"); // Reserved "attribute" field
+ writeUleb128(os, out.typeSec->lookupType(t->signature), "sig index");
}
}
internalGotSymbols.push_back(sym);
}
-void GlobalSection::generateRelocationCode(raw_ostream &os) const {
- bool is64 = config->is64.getValueOr(false);
+void GlobalSection::generateRelocationCode(raw_ostream &os, bool TLS) const {
+ assert(!config->extendedConst);
+ bool is64 = config->is64.value_or(false);
unsigned opcode_ptr_const = is64 ? WASM_OPCODE_I64_CONST
: WASM_OPCODE_I32_CONST;
unsigned opcode_ptr_add = is64 ? WASM_OPCODE_I64_ADD
: WASM_OPCODE_I32_ADD;
for (const Symbol *sym : internalGotSymbols) {
+ if (TLS != sym->isTLS())
+ continue;
+
if (auto *d = dyn_cast<DefinedData>(sym)) {
// Get __memory_base
writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
- writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "__memory_base");
+ if (sym->isTLS())
+ writeUleb128(os, WasmSym::tlsBase->getGlobalIndex(), "__tls_base");
+ else
+ writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(),
+ "__memory_base");
// Add the virtual address of the data symbol
writeU8(os, opcode_ptr_const, "CONST");
writeGlobalType(os, g->getType());
writeInitExpr(os, g->getInitExpr());
}
- bool is64 = config->is64.getValueOr(false);
+ bool is64 = config->is64.value_or(false);
uint8_t itype = is64 ? WASM_TYPE_I64 : WASM_TYPE_I32;
for (const Symbol *sym : internalGotSymbols) {
- // In the case of dynamic linking, internal GOT entries
- // need to be mutable since they get updated to the correct
- // runtime value during `__wasm_apply_global_relocs`.
- bool mutable_ = config->isPic & !sym->isStub;
- WasmGlobalType type{itype, mutable_};
- WasmInitExpr initExpr;
- if (auto *d = dyn_cast<DefinedData>(sym))
- initExpr = intConst(d->getVA(), is64);
- else if (auto *f = dyn_cast<FunctionSymbol>(sym))
- initExpr = intConst(f->isStub ? 0 : f->getTableIndex(), is64);
- else {
- assert(isa<UndefinedData>(sym));
- initExpr = intConst(0, is64);
+ bool mutable_ = false;
+ if (!sym->isStub) {
+ // In the case of dynamic linking, unless we have 'extended-const'
+ // available, these global must to be mutable since they get updated to
+ // the correct runtime value during `__wasm_apply_global_relocs`.
+ if (!config->extendedConst && config->isPic && !sym->isTLS())
+ mutable_ = true;
+ // With multi-theadeding any TLS globals must be mutable since they get
+ // set during `__wasm_apply_global_tls_relocs`
+ if (config->sharedMemory && sym->isTLS())
+ mutable_ = true;
}
+ WasmGlobalType type{itype, mutable_};
writeGlobalType(os, type);
- writeInitExpr(os, initExpr);
+
+ if (config->extendedConst && config->isPic && !sym->isTLS() &&
+ isa<DefinedData>(sym)) {
+ // We can use an extended init expression to add a constant
+ // offset of __memory_base.
+ auto *d = cast<DefinedData>(sym);
+ writeU8(os, WASM_OPCODE_GLOBAL_GET, "global get");
+ writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(),
+ "literal (global index)");
+ if (d->getVA()) {
+ writePtrConst(os, d->getVA(), is64, "offset");
+ writeU8(os, is64 ? WASM_OPCODE_I64_ADD : WASM_OPCODE_I32_ADD, "add");
+ }
+ writeU8(os, WASM_OPCODE_END, "opcode:end");
+ } else {
+ WasmInitExpr initExpr;
+ if (auto *d = dyn_cast<DefinedData>(sym))
+ initExpr = intConst(d->getVA(), is64);
+ else if (auto *f = dyn_cast<FunctionSymbol>(sym))
+ initExpr = intConst(f->isStub ? 0 : f->getTableIndex(), is64);
+ else {
+ assert(isa<UndefinedData>(sym));
+ initExpr = intConst(0, is64);
+ }
+ writeInitExpr(os, initExpr);
+ }
}
for (const DefinedData *sym : dataAddressGlobals) {
WasmGlobalType type{itype, false};
writeUleb128(os, tableNumber, "table number");
WasmInitExpr initExpr;
+ initExpr.Extended = false;
if (config->isPic) {
- initExpr.Opcode = WASM_OPCODE_GLOBAL_GET;
- initExpr.Value.Global =
- (config->is64.getValueOr(false) ? WasmSym::tableBase32
- : WasmSym::tableBase)
+ initExpr.Inst.Opcode = WASM_OPCODE_GLOBAL_GET;
+ initExpr.Inst.Value.Global =
+ (config->is64.value_or(false) ? WasmSym::tableBase32
+ : WasmSym::tableBase)
->getGlobalIndex();
} else {
- initExpr.Opcode = WASM_OPCODE_I32_CONST;
- initExpr.Value.Int32 = config->tableBase;
+ initExpr.Inst.Opcode = WASM_OPCODE_I32_CONST;
+ initExpr.Inst.Value.Int32 = config->tableBase;
}
writeInitExpr(os, initExpr);
uint32_t tableIndex = config->tableBase;
for (const FunctionSymbol *sym : indirectFunctions) {
assert(sym->getTableIndex() == tableIndex);
+ (void) tableIndex;
writeUleb128(os, sym->getFunctionIndex(), "function index");
++tableIndex;
}
DataCountSection::DataCountSection(ArrayRef<OutputSegment *> segments)
: SyntheticSection(llvm::wasm::WASM_SEC_DATACOUNT),
- numSegments(std::count_if(
- segments.begin(), segments.end(),
- [](OutputSegment *const segment) { return !segment->isBss; })) {}
+ numSegments(llvm::count_if(segments, [](OutputSegment *const segment) {
+ return segment->requiredInBinary();
+ })) {}
void DataCountSection::writeBody() {
writeUleb128(bodyOutputStream, numSegments, "data count");
writeUleb128(sub.os, flags, "sym flags");
if (auto *f = dyn_cast<FunctionSymbol>(sym)) {
- writeUleb128(sub.os, f->getFunctionIndex(), "index");
+ if (auto *d = dyn_cast<DefinedFunction>(sym)) {
+ writeUleb128(sub.os, d->getExportedFunctionIndex(), "index");
+ } else {
+ writeUleb128(sub.os, f->getFunctionIndex(), "index");
+ }
if (sym->isDefined() || (flags & WASM_SYMBOL_EXPLICIT_NAME) != 0)
writeStr(sub.os, sym->getName(), "sym name");
} else if (auto *g = dyn_cast<GlobalSymbol>(sym)) {
unsigned numNames = out.importSec->getNumImportedFunctions();
for (const InputFunction *f : out.functionSec->inputFunctions)
- if (!f->getName().empty() || !f->getDebugName().empty())
+ if (!f->name.empty() || !f->debugName.empty())
++numNames;
return numNames;
unsigned numNames = 0;
for (const OutputSegment *s : segments)
- if (!s->name.empty() && !s->isBss)
+ if (!s->name.empty() && s->requiredInBinary())
++numNames;
return numNames;
}
}
for (const InputFunction *f : out.functionSec->inputFunctions) {
- if (!f->getName().empty()) {
+ if (!f->name.empty()) {
writeUleb128(sub.os, f->getFunctionIndex(), "func index");
- if (!f->getDebugName().empty()) {
- writeStr(sub.os, f->getDebugName(), "symbol name");
+ if (!f->debugName.empty()) {
+ writeStr(sub.os, f->debugName, "symbol name");
} else {
- writeStr(sub.os, maybeDemangleSymbol(f->getName()), "symbol name");
+ writeStr(sub.os, maybeDemangleSymbol(f->name), "symbol name");
}
}
}
writeUleb128(sub.os, count, "name count");
for (OutputSegment *s : segments) {
- if (!s->name.empty() && !s->isBss) {
+ if (!s->name.empty() && s->requiredInBinary()) {
writeUleb128(sub.os, s->index, "global index");
writeStr(sub.os, s->name, "segment name");
}
{std::make_pair(&info.Languages, &languages),
std::make_pair(&info.Tools, &tools), std::make_pair(&info.SDKs, &sDKs)})
for (auto &producer : *producers.first)
- if (producers.second->end() ==
- llvm::find_if(*producers.second,
+ if (llvm::none_of(*producers.second,
[&](std::pair<std::string, std::string> seen) {
return seen.first == producer.first;
}))
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/BinaryFormat/WasmTraits.h"
+#include <optional>
#define DEBUG_TYPE "lld"
// Create the custom "dylink" section containing information for the dynamic
// linker.
// See
-// https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md
+// https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md
class DylinkSection : public SyntheticSection {
public:
- DylinkSection() : SyntheticSection(llvm::wasm::WASM_SEC_CUSTOM, "dylink") {}
- bool isNeeded() const override { return config->isPic; }
+ DylinkSection() : SyntheticSection(llvm::wasm::WASM_SEC_CUSTOM, "dylink.0") {}
+ bool isNeeded() const override;
void writeBody() override;
uint32_t memAlign = 0;
public:
T type;
- llvm::Optional<StringRef> importModule;
- llvm::Optional<StringRef> importName;
+ std::optional<StringRef> importModule;
+ std::optional<StringRef> importName;
State state;
public:
ImportKey(T type) : type(type), state(State::Plain) {}
ImportKey(T type, State state) : type(type), state(state) {}
- ImportKey(T type, llvm::Optional<StringRef> importModule,
- llvm::Optional<StringRef> importName)
+ ImportKey(T type, std::optional<StringRef> importModule,
+ std::optional<StringRef> importName)
: type(type), importModule(importModule), importName(importName),
state(State::Plain) {}
};
llvm::DenseMap<ImportKey<WasmGlobalType>, uint32_t> importedGlobals;
llvm::DenseMap<ImportKey<WasmSignature>, uint32_t> importedFunctions;
llvm::DenseMap<ImportKey<WasmTableType>, uint32_t> importedTables;
+ llvm::DenseMap<ImportKey<WasmSignature>, uint32_t> importedTags;
};
class FunctionSection : public SyntheticSection {
public:
MemorySection() : SyntheticSection(llvm::wasm::WASM_SEC_MEMORY) {}
- bool isNeeded() const override { return !config->importMemory; }
+ bool isNeeded() const override { return !config->memoryImport.has_value(); }
void writeBody() override;
uint64_t numMemoryPages = 0;
// specific relocation types combined with linker relaxation which could
// transform a `global.get` to an `i32.const`.
void addInternalGOTEntry(Symbol *sym);
- bool needsRelocations() { return internalGotSymbols.size(); }
- void generateRelocationCode(raw_ostream &os) const;
+ bool needsRelocations() {
+ if (config->extendedConst)
+ return false;
+ return llvm::any_of(internalGotSymbols,
+ [=](Symbol *sym) { return !sym->isTLS(); });
+ }
+ bool needsTLSRelocations() {
+ return llvm::any_of(internalGotSymbols,
+ [=](Symbol *sym) { return sym->isTLS(); });
+ }
+ void generateRelocationCode(raw_ostream &os, bool TLS) const;
- std::vector<const DefinedData *> dataAddressGlobals;
+ std::vector<DefinedData *> dataAddressGlobals;
std::vector<InputGlobal *> inputGlobals;
std::vector<Symbol *> internalGotSymbols;
#include "SymbolTable.h"
#include "SyntheticSections.h"
#include "WriterUtils.h"
-#include "lld/Common/ErrorHandler.h"
-#include "lld/Common/Memory.h"
+#include "lld/Common/CommonLinkerContext.h"
#include "lld/Common/Strings.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallSet.h"
#include <cstdarg>
#include <map>
+#include <optional>
#define DEBUG_TYPE "lld"
void createStartFunction();
void createApplyDataRelocationsFunction();
void createApplyGlobalRelocationsFunction();
+ void createApplyGlobalTLSRelocationsFunction();
void createCallCtorsFunction();
void createInitTLSFunction();
void createCommandExportWrappers();
void populateSymtab();
void populateProducers();
void populateTargetFeatures();
+ // populateTargetFeatures happens early on so some checks are delayed
+ // until imports and exports are finalized. There are run unstead
+ // in checkImportExportTargetFeatures
+ void checkImportExportTargetFeatures();
void calculateInitFunctions();
void calculateImports();
void calculateExports();
// Exclude COMDAT sections that are not selected for inclusion
if (section->discarded)
continue;
- StringRef name = section->getName();
+ StringRef name = section->name;
// These custom sections are known the linker and synthesized rather than
// blindly copied.
if (name == "linking" || name == "name" || name == "producers" ||
else if (sec->type == WASM_SEC_CODE)
name = "reloc.CODE";
else if (sec->type == WASM_SEC_CUSTOM)
- name = saver.save("reloc." + sec->name);
+ name = saver().save("reloc." + sec->name);
else
llvm_unreachable(
"relocations only supported for code, data, or custom sections");
}
static void setGlobalPtr(DefinedGlobal *g, uint64_t memoryPtr) {
+ LLVM_DEBUG(dbgs() << "setGlobalPtr " << g->getName() << " -> " << memoryPtr << "\n");
g->global->setPointerValue(memoryPtr);
}
// to each of the input data sections as well as the explicit stack region.
// The default memory layout is as follows, from low to high.
//
-// - initialized data (starting at Config->globalBase)
+// - initialized data (starting at config->globalBase)
// - BSS data (not currently implemented in llvm)
-// - explicit stack (Config->ZStackSize)
+// - explicit stack (config->ZStackSize)
// - heap start / unallocated
//
// The --stack-first option means that stack is placed before any static data.
if (config->relocatable || config->isPic)
return;
memoryPtr = alignTo(memoryPtr, stackAlignment);
+ if (WasmSym::stackLow)
+ WasmSym::stackLow->setVA(memoryPtr);
if (config->zStackSize != alignTo(config->zStackSize, stackAlignment))
error("stack size must be " + Twine(stackAlignment) + "-byte aligned");
log("mem: stack size = " + Twine(config->zStackSize));
log("mem: stack base = " + Twine(memoryPtr));
memoryPtr += config->zStackSize;
setGlobalPtr(cast<DefinedGlobal>(WasmSym::stackPointer), memoryPtr);
+ if (WasmSym::stackHigh)
+ WasmSym::stackHigh->setVA(memoryPtr);
log("mem: stack top = " + Twine(memoryPtr));
};
if (config->stackFirst) {
placeStack();
+ if (config->globalBase) {
+ if (config->globalBase < memoryPtr) {
+ error("--global-base cannot be less than stack size when --stack-first is used");
+ return;
+ }
+ memoryPtr = config->globalBase;
+ }
} else {
+ if (!config->globalBase && !config->relocatable && !config->isPic) {
+ // The default offset for static/global data, for when --global-base is
+ // not specified on the command line. The precise value of 1024 is
+ // somewhat arbitrary, and pre-dates wasm-ld (Its the value that
+ // emscripten used prior to wasm-ld).
+ config->globalBase = 1024;
+ }
memoryPtr = config->globalBase;
- log("mem: global base = " + Twine(config->globalBase));
}
+ log("mem: global base = " + Twine(memoryPtr));
if (WasmSym::globalBase)
WasmSym::globalBase->setVA(memoryPtr);
memoryPtr, seg->size, seg->alignment));
if (!config->relocatable && seg->isTLS()) {
- if (config->sharedMemory) {
+ if (WasmSym::tlsSize) {
auto *tlsSize = cast<DefinedGlobal>(WasmSym::tlsSize);
setGlobalPtr(tlsSize, seg->size);
-
+ }
+ if (WasmSym::tlsAlign) {
auto *tlsAlign = cast<DefinedGlobal>(WasmSym::tlsAlign);
setGlobalPtr(tlsAlign, int64_t{1} << seg->alignment);
- } else {
+ }
+ if (!config->sharedMemory && WasmSym::tlsBase) {
auto *tlsBase = cast<DefinedGlobal>(WasmSym::tlsBase);
setGlobalPtr(tlsBase, memoryPtr);
}
WasmSym::heapBase->setVA(memoryPtr);
}
- uint64_t maxMemorySetting = 1ULL
- << (config->is64.getValueOr(false) ? 48 : 32);
+ uint64_t maxMemorySetting = 1ULL << (config->is64.value_or(false) ? 48 : 32);
if (config->initialMemory != 0) {
if (config->initialMemory != alignTo(config->initialMemory, WasmPageSize))
Twine(maxMemorySetting));
memoryPtr = config->initialMemory;
}
- out.memorySec->numMemoryPages =
- alignTo(memoryPtr, WasmPageSize) / WasmPageSize;
+
+ memoryPtr = alignTo(memoryPtr, WasmPageSize);
+
+ out.memorySec->numMemoryPages = memoryPtr / WasmPageSize;
log("mem: total pages = " + Twine(out.memorySec->numMemoryPages));
+ if (WasmSym::heapEnd) {
+ // Set `__heap_end` to follow the end of the statically allocated linear
+ // memory. The fact that this comes last means that a malloc/brk
+ // implementation can grow the heap at runtime.
+ log("mem: heap end = " + Twine(memoryPtr));
+ WasmSym::heapEnd->setVA(memoryPtr);
+ }
+
if (config->maxMemory != 0) {
if (config->maxMemory != alignTo(config->maxMemory, WasmPageSize))
error("maximum memory must be " + Twine(WasmPageSize) + "-byte aligned");
if (config->isPic)
max = maxMemorySetting;
else
- max = alignTo(memoryPtr, WasmPageSize);
+ max = memoryPtr;
}
out.memorySec->maxMemoryPages = max / WasmPageSize;
log("mem: max pages = " + Twine(out.memorySec->maxMemoryPages));
LLVM_DEBUG(dbgs() << "addStartStopSymbols: " << name << "\n");
uint64_t start = seg->startVA;
uint64_t stop = start + seg->size;
- symtab->addOptionalDataSymbol(saver.save("__start_" + name), start);
- symtab->addOptionalDataSymbol(saver.save("__stop_" + name), stop);
+ symtab->addOptionalDataSymbol(saver().save("__start_" + name), start);
+ symtab->addOptionalDataSymbol(saver().save("__stop_" + name), stop);
}
void Writer::addSections() {
SmallSet<std::string, 8> &allowed = out.targetFeaturesSec->features;
bool tlsUsed = false;
+ if (config->isPic) {
+ // This should not be necessary because all PIC objects should
+ // contain the mutable-globals feature.
+ // TODO(https://bugs.llvm.org/show_bug.cgi?id=52339)
+ allowed.insert("mutable-globals");
+ }
+
+ if (config->extraFeatures.has_value()) {
+ auto &extraFeatures = *config->extraFeatures;
+ allowed.insert(extraFeatures.begin(), extraFeatures.end());
+ }
+
// Only infer used features if user did not specify features
- bool inferFeatures = !config->features.hasValue();
+ bool inferFeatures = !config->features.has_value();
if (!inferFeatures) {
- auto &explicitFeatures = config->features.getValue();
+ auto &explicitFeatures = *config->features;
allowed.insert(explicitFeatures.begin(), explicitFeatures.end());
if (!config->checkFeatures)
- return;
+ goto done;
}
// Find the sets of used, required, and disallowed features
auto isTLS = [](InputChunk *segment) {
return segment->live && segment->isTLS();
};
- tlsUsed = tlsUsed ||
- std::any_of(file->segments.begin(), file->segments.end(), isTLS);
+ tlsUsed = tlsUsed || llvm::any_of(file->segments, isTLS);
}
if (inferFeatures)
allowed.insert(std::string(key));
if (!config->checkFeatures)
- return;
-
- if (!config->relocatable && allowed.count("mutable-globals") == 0) {
- for (const Symbol *sym : out.importSec->importedSymbols) {
- if (auto *global = dyn_cast<GlobalSymbol>(sym)) {
- if (global->getGlobalType()->Mutable) {
- error(Twine("mutable global imported but 'mutable-globals' feature "
- "not present in inputs: `") +
- toString(*sym) + "`. Use --no-check-features to suppress.");
- }
- }
- }
- for (const Symbol *sym : out.exportSec->exportedSymbols) {
- if (isa<GlobalSymbol>(sym)) {
- error(Twine("mutable global exported but 'mutable-globals' feature "
- "not present in inputs: `") +
- toString(*sym) + "`. Use --no-check-features to suppress.");
- }
- }
- }
+ goto done;
if (config->sharedMemory) {
if (disallowed.count("shared-mem"))
// Validate that used features are allowed in output
if (!inferFeatures) {
- for (auto &feature : used.keys()) {
+ for (const auto &feature : used.keys()) {
if (!allowed.count(std::string(feature)))
error(Twine("Target feature '") + feature + "' used by " +
used[feature] + " is not allowed.");
for (ObjFile *file : symtab->objectFiles) {
StringRef fileName(file->getName());
SmallSet<std::string, 8> objectFeatures;
- for (auto &feature : file->getWasmObj()->getTargetFeatures()) {
+ for (const auto &feature : file->getWasmObj()->getTargetFeatures()) {
if (feature.Prefix == WASM_FEATURE_PREFIX_DISALLOWED)
continue;
objectFeatures.insert(feature.Name);
fileName + " is disallowed by " + disallowed[feature.Name] +
". Use --no-check-features to suppress.");
}
- for (auto &feature : required.keys()) {
+ for (const auto &feature : required.keys()) {
if (!objectFeatures.count(std::string(feature)))
error(Twine("Missing target feature '") + feature + "' in " + fileName +
", required by " + required[feature] +
". Use --no-check-features to suppress.");
}
}
+
+done:
+ // Normally we don't include bss segments in the binary. In particular if
+ // memory is not being imported then we can assume its zero initialized.
+ // In the case the memory is imported, and we can use the memory.fill
+ // instruction, then we can also avoid including the segments.
+ if (config->memoryImport.has_value() && !allowed.count("bulk-memory"))
+ config->emitBssSegments = true;
+
+ if (allowed.count("extended-const"))
+ config->extendedConst = true;
+
+ for (auto &feature : allowed)
+ log("Allowed feature: " + feature);
+}
+
+void Writer::checkImportExportTargetFeatures() {
+ if (config->relocatable || !config->checkFeatures)
+ return;
+
+ if (out.targetFeaturesSec->features.count("mutable-globals") == 0) {
+ for (const Symbol *sym : out.importSec->importedSymbols) {
+ if (auto *global = dyn_cast<GlobalSymbol>(sym)) {
+ if (global->getGlobalType()->Mutable) {
+ error(Twine("mutable global imported but 'mutable-globals' feature "
+ "not present in inputs: `") +
+ toString(*sym) + "`. Use --no-check-features to suppress.");
+ }
+ }
+ }
+ for (const Symbol *sym : out.exportSec->exportedSymbols) {
+ if (isa<GlobalSymbol>(sym)) {
+ error(Twine("mutable global exported but 'mutable-globals' feature "
+ "not present in inputs: `") +
+ toString(*sym) + "`. Use --no-check-features to suppress.");
+ }
+ }
+ }
}
static bool shouldImport(Symbol *sym) {
- if (!sym->isUndefined())
- return false;
- if (sym->isWeak() && !config->relocatable && !config->isPic)
+ // We don't generate imports for data symbols. They however can be imported
+ // as GOT entries.
+ if (isa<DataSymbol>(sym))
return false;
if (!sym->isLive())
return false;
if (!sym->isUsedInRegularObj)
return false;
- // We don't generate imports for data symbols. They however can be imported
- // as GOT entries.
- if (isa<DataSymbol>(sym))
+ // When a symbol is weakly defined in a shared library we need to allow
+ // it to be overridden by another module so need to both import
+ // and export the symbol.
+ if (config->shared && sym->isWeak() && !sym->isUndefined() &&
+ !sym->isHidden())
+ return true;
+ if (!sym->isUndefined())
return false;
+ if (sym->isWeak() && !config->relocatable && !config->isPic)
+ return false;
+
+ // In PIC mode we only need to import functions when they are called directly.
+ // Indirect usage all goes via GOT imports.
+ if (config->isPic) {
+ if (auto *f = dyn_cast<UndefinedFunction>(sym))
+ if (!f->isCalledDirectly)
+ return false;
+ }
- if (config->isPic || config->relocatable || config->importUndefined)
+ if (config->isPic || config->relocatable || config->importUndefined ||
+ config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic)
return true;
if (config->allowUndefinedSymbols.count(sym->getName()) != 0)
return true;
- return sym->importName.hasValue();
+ return sym->isImported();
}
void Writer::calculateImports() {
shouldImport(WasmSym::indirectFunctionTable))
out.importSec->addImport(WasmSym::indirectFunctionTable);
- for (Symbol *sym : symtab->getSymbols()) {
+ for (Symbol *sym : symtab->symbols()) {
if (!shouldImport(sym))
continue;
if (sym == WasmSym::indirectFunctionTable)
if (config->relocatable)
return;
- if (!config->relocatable && !config->importMemory)
+ if (!config->relocatable && config->memoryExport.has_value()) {
out.exportSec->exports.push_back(
- WasmExport{"memory", WASM_EXTERNAL_MEMORY, 0});
+ WasmExport{*config->memoryExport, WASM_EXTERNAL_MEMORY, 0});
+ }
unsigned globalIndex =
out.importSec->getNumImportedGlobals() + out.globalSec->numGlobals();
- for (Symbol *sym : symtab->getSymbols()) {
+ for (Symbol *sym : symtab->symbols()) {
if (!sym->isExported())
continue;
if (!sym->isLive())
StringRef name = sym->getName();
WasmExport export_;
if (auto *f = dyn_cast<DefinedFunction>(sym)) {
- if (Optional<StringRef> exportName = f->function->getExportName()) {
+ if (std::optional<StringRef> exportName = f->function->getExportName()) {
name = *exportName;
}
- export_ = {name, WASM_EXTERNAL_FUNCTION, f->getFunctionIndex()};
+ export_ = {name, WASM_EXTERNAL_FUNCTION, f->getExportedFunctionIndex()};
} else if (auto *g = dyn_cast<DefinedGlobal>(sym)) {
if (g->getGlobalType()->Mutable && !g->getFile() && !g->forceExport) {
// Avoid exporting mutable globals are linker synthesized (e.g.
} else if (auto *t = dyn_cast<DefinedTag>(sym)) {
export_ = {name, WASM_EXTERNAL_TAG, t->getTagIndex()};
} else if (auto *d = dyn_cast<DefinedData>(sym)) {
- if (d->segment && d->segment->isTLS()) {
- // We can't currenly export TLS data symbols.
- if (sym->isExportedExplicit())
- error("TLS symbols cannot yet be exported: `" + toString(*sym) + "`");
- continue;
- }
out.globalSec->dataAddressGlobals.push_back(d);
export_ = {name, WASM_EXTERNAL_GLOBAL, globalIndex++};
} else {
if (!config->relocatable && !config->emitRelocs)
return;
- for (Symbol *sym : symtab->getSymbols())
+ for (Symbol *sym : symtab->symbols())
if (sym->isUsedInRegularObj && sym->isLive())
out.linkingSec->addToSymtab(sym);
// If there are no ctors and there's no libc `__wasm_call_dtors` to
// call, don't wrap the exports.
- if (initFunctions.empty() && WasmSym::callDtors == NULL)
+ if (initFunctions.empty() && WasmSym::callDtors == nullptr)
return;
std::vector<DefinedFunction *> toWrap;
- for (Symbol *sym : symtab->getSymbols())
+ for (Symbol *sym : symtab->symbols())
if (sym->isExported())
if (auto *f = dyn_cast<DefinedFunction>(sym))
toWrap.push_back(f);
const std::string &funcName = commandExportWrapperNames.back();
auto func = make<SyntheticFunction>(*f->getSignature(), funcName);
- if (f->function->getExportName().hasValue())
+ if (f->function->getExportName())
func->setExportName(f->function->getExportName()->str());
else
func->setExportName(f->getName().str());
// symbols are be relative to single __tls_base.
if (seg.isTLS())
return ".tdata";
- StringRef name = seg.getName();
if (!config->mergeDataSegments)
- return name;
- if (name.startswith(".text."))
+ return seg.name;
+ if (seg.name.startswith(".text."))
return ".text";
- if (name.startswith(".data."))
+ if (seg.name.startswith(".data."))
return ".data";
- if (name.startswith(".bss."))
+ if (seg.name.startswith(".bss."))
return ".bss";
- if (name.startswith(".rodata."))
+ if (seg.name.startswith(".rodata."))
return ".rodata";
- return name;
+ return seg.name;
}
OutputSegment *Writer::createOutputSegment(StringRef name) {
OutputSegment *s = make<OutputSegment>(name);
if (config->sharedMemory)
s->initFlags = WASM_DATA_SEGMENT_IS_PASSIVE;
- // Exported memories are guaranteed to be zero-initialized, so no need
- // to emit data segments for bss sections.
- // TODO: consider initializing bss sections with memory.fill
- // instructions when memory is imported and bulk-memory is available.
- if (!config->importMemory && !config->relocatable && name.startswith(".bss"))
+ if (!config->relocatable && name.startswith(".bss"))
s->isBss = true;
segments.push_back(s);
return s;
// With PIC code we currently only support a single active data segment since
// we only have a single __memory_base to use as our base address. This pass
// combines all data segments into a single .data segment.
- // This restructions can be relaxed once we have extended constant
- // expressions available:
- // https://github.com/WebAssembly/extended-const
+ // This restriction does not apply when the extended const extension is
+ // available: https://github.com/WebAssembly/extended-const
+ assert(!config->extendedConst);
assert(config->isPic && !config->sharedMemory);
if (segments.size() <= 1)
return;
combined->addInputSegment(inSeg);
#ifndef NDEBUG
uint64_t newVA = inSeg->getVA();
- LLVM_DEBUG(dbgs() << "added input segment. name=" << inSeg->getName()
+ LLVM_DEBUG(dbgs() << "added input segment. name=" << inSeg->name
<< " oldVA=" << oldVA << " newVA=" << newVA << "\n");
assert(oldVA == newVA);
#endif
writeUleb128(os, bodyContent.size(), "function size");
os << bodyContent;
}
- ArrayRef<uint8_t> body = arrayRefFromStringRef(saver.save(functionBody));
+ ArrayRef<uint8_t> body = arrayRefFromStringRef(saver().save(functionBody));
cast<SyntheticFunction>(func->function)->setBody(body);
}
bool Writer::needsPassiveInitialization(const OutputSegment *segment) {
- return segment->initFlags & WASM_DATA_SEGMENT_IS_PASSIVE &&
- !segment->isTLS() && !segment->isBss;
+ // If bulk memory features is supported then we can perform bss initialization
+ // (via memory.fill) during `__wasm_init_memory`.
+ if (config->memoryImport.has_value() && !segment->requiredInBinary())
+ return true;
+ return segment->initFlags & WASM_DATA_SEGMENT_IS_PASSIVE;
}
bool Writer::hasPassiveInitializedSegments() {
- return std::find_if(segments.begin(), segments.end(),
- [this](const OutputSegment *s) {
- return this->needsPassiveInitialization(s);
- }) != segments.end();
+ return llvm::any_of(segments, [this](const OutputSegment *s) {
+ return this->needsPassiveInitialization(s);
+ });
}
void Writer::createSyntheticInitFunctions() {
// Passive segments are used to avoid memory being reinitialized on each
// thread's instantiation. These passive segments are initialized and
// dropped in __wasm_init_memory, which is registered as the start function
- if (config->sharedMemory && hasPassiveInitializedSegments()) {
+ // We also initialize bss segments (using memory.fill) as part of this
+ // function.
+ if (hasPassiveInitializedSegments()) {
WasmSym::initMemory = symtab->addSyntheticFunction(
"__wasm_init_memory", WASM_SYMBOL_VISIBILITY_HIDDEN,
make<SyntheticFunction>(nullSignature, "__wasm_init_memory"));
WasmSym::initMemory->markLive();
+ if (config->sharedMemory) {
+ // This global is assigned during __wasm_init_memory in the shared memory
+ // case.
+ WasmSym::tlsBase->markLive();
+ }
}
- if (config->isPic) {
- // For PIC code we create synthetic functions that apply relocations.
- // These get called from __wasm_call_ctors before the user-level
- // constructors.
- WasmSym::applyDataRelocs = symtab->addSyntheticFunction(
- "__wasm_apply_data_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
- make<SyntheticFunction>(nullSignature, "__wasm_apply_data_relocs"));
- WasmSym::applyDataRelocs->markLive();
-
- if (out.globalSec->needsRelocations()) {
- WasmSym::applyGlobalRelocs = symtab->addSyntheticFunction(
- "__wasm_apply_global_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
- make<SyntheticFunction>(nullSignature, "__wasm_apply_global_relocs"));
- WasmSym::applyGlobalRelocs->markLive();
- }
+ if (config->sharedMemory && out.globalSec->needsTLSRelocations()) {
+ WasmSym::applyGlobalTLSRelocs = symtab->addSyntheticFunction(
+ "__wasm_apply_global_tls_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
+ make<SyntheticFunction>(nullSignature,
+ "__wasm_apply_global_tls_relocs"));
+ WasmSym::applyGlobalTLSRelocs->markLive();
+ // TLS relocations depend on the __tls_base symbols
+ WasmSym::tlsBase->markLive();
}
+ if (config->isPic && out.globalSec->needsRelocations()) {
+ WasmSym::applyGlobalRelocs = symtab->addSyntheticFunction(
+ "__wasm_apply_global_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
+ make<SyntheticFunction>(nullSignature, "__wasm_apply_global_relocs"));
+ WasmSym::applyGlobalRelocs->markLive();
+ }
+
+ // If there is only one start function we can just use that function
+ // itself as the Wasm start function, otherwise we need to synthesize
+ // a new function to call them in sequence.
if (WasmSym::applyGlobalRelocs && WasmSym::initMemory) {
WasmSym::startFunction = symtab->addSyntheticFunction(
"__wasm_start", WASM_SYMBOL_VISIBILITY_HIDDEN,
void Writer::createInitMemoryFunction() {
LLVM_DEBUG(dbgs() << "createInitMemoryFunction\n");
assert(WasmSym::initMemory);
- assert(WasmSym::initMemoryFlag);
assert(hasPassiveInitializedSegments());
- uint64_t flagAddress = WasmSym::initMemoryFlag->getVA();
- bool is64 = config->is64.getValueOr(false);
+ uint64_t flagAddress;
+ if (config->sharedMemory) {
+ assert(WasmSym::initMemoryFlag);
+ flagAddress = WasmSym::initMemoryFlag->getVA();
+ }
+ bool is64 = config->is64.value_or(false);
std::string bodyContent;
{
raw_string_ostream os(bodyContent);
// Initialize memory in a thread-safe manner. The thread that successfully
- // increments the flag from 0 to 1 is is responsible for performing the
- // memory initialization. Other threads go sleep on the flag until the
- // first thread finishing initializing memory, increments the flag to 2,
- // and wakes all the other threads. Once the flag has been set to 2,
- // subsequently started threads will skip the sleep. All threads
- // unconditionally drop their passive data segments once memory has been
- // initialized. The generated code is as follows:
+ // increments the flag from 0 to 1 is responsible for performing the memory
+ // initialization. Other threads go sleep on the flag until the first thread
+ // finishing initializing memory, increments the flag to 2, and wakes all
+ // the other threads. Once the flag has been set to 2, subsequently started
+ // threads will skip the sleep. All threads unconditionally drop their
+ // passive data segments once memory has been initialized. The generated
+ // code is as follows:
//
// (func $__wasm_init_memory
- // (if
- // (i32.atomic.rmw.cmpxchg align=2 offset=0
- // (i32.const $__init_memory_flag)
- // (i32.const 0)
- // (i32.const 1)
- // )
- // (then
- // (drop
- // (i32.atomic.wait align=2 offset=0
- // (i32.const $__init_memory_flag)
- // (i32.const 1)
- // (i32.const -1)
+ // (block $drop
+ // (block $wait
+ // (block $init
+ // (br_table $init $wait $drop
+ // (i32.atomic.rmw.cmpxchg align=2 offset=0
+ // (i32.const $__init_memory_flag)
+ // (i32.const 0)
+ // (i32.const 1)
+ // )
// )
- // )
- // )
- // (else
+ // ) ;; $init
// ( ... initialize data segments ... )
// (i32.atomic.store align=2 offset=0
// (i32.const $__init_memory_flag)
// (i32.const -1u)
// )
// )
+ // (br $drop)
+ // ) ;; $wait
+ // (drop
+ // (i32.atomic.wait align=2 offset=0
+ // (i32.const $__init_memory_flag)
+ // (i32.const 1)
+ // (i32.const -1)
+ // )
// )
- // )
+ // ) ;; $drop
// ( ... drop data segments ... )
// )
//
// (i32.const $__init_memory_flag)
// (i32.const 1)
- // With PIC code we cache the flag address in local 0
- if (config->isPic) {
- writeUleb128(os, 1, "num local decls");
- writeUleb128(os, 1, "local count");
- writeU8(os, is64 ? WASM_TYPE_I64 : WASM_TYPE_I32, "address type");
- writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
- writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base");
- writePtrConst(os, flagAddress, is64, "flag address");
- writeU8(os, is64 ? WASM_OPCODE_I64_ADD : WASM_OPCODE_I32_ADD, "add");
- writeU8(os, WASM_OPCODE_LOCAL_SET, "local.set");
- writeUleb128(os, 0, "local 0");
- } else {
- writeUleb128(os, 0, "num locals");
- }
-
auto writeGetFlagAddress = [&]() {
if (config->isPic) {
writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
}
};
- // Atomically check whether this is the main thread.
- writeGetFlagAddress();
- writeI32Const(os, 0, "expected flag value");
- writeI32Const(os, 1, "flag value");
- writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
- writeUleb128(os, WASM_OPCODE_I32_RMW_CMPXCHG, "i32.atomic.rmw.cmpxchg");
- writeMemArg(os, 2, 0);
- writeU8(os, WASM_OPCODE_IF, "IF");
- writeU8(os, WASM_TYPE_NORESULT, "blocktype");
-
- // Did not increment 0, so wait for main thread to initialize memory
- writeGetFlagAddress();
- writeI32Const(os, 1, "expected flag value");
- writeI64Const(os, -1, "timeout");
-
- writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
- writeUleb128(os, WASM_OPCODE_I32_ATOMIC_WAIT, "i32.atomic.wait");
- writeMemArg(os, 2, 0);
- writeU8(os, WASM_OPCODE_DROP, "drop");
-
- writeU8(os, WASM_OPCODE_ELSE, "ELSE");
-
- // Did increment 0, so conditionally initialize passive data segments
+ if (config->sharedMemory) {
+ // With PIC code we cache the flag address in local 0
+ if (config->isPic) {
+ writeUleb128(os, 1, "num local decls");
+ writeUleb128(os, 2, "local count");
+ writeU8(os, is64 ? WASM_TYPE_I64 : WASM_TYPE_I32, "address type");
+ writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
+ writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base");
+ writePtrConst(os, flagAddress, is64, "flag address");
+ writeU8(os, is64 ? WASM_OPCODE_I64_ADD : WASM_OPCODE_I32_ADD, "add");
+ writeU8(os, WASM_OPCODE_LOCAL_SET, "local.set");
+ writeUleb128(os, 0, "local 0");
+ } else {
+ writeUleb128(os, 0, "num locals");
+ }
+
+ // Set up destination blocks
+ writeU8(os, WASM_OPCODE_BLOCK, "block $drop");
+ writeU8(os, WASM_TYPE_NORESULT, "block type");
+ writeU8(os, WASM_OPCODE_BLOCK, "block $wait");
+ writeU8(os, WASM_TYPE_NORESULT, "block type");
+ writeU8(os, WASM_OPCODE_BLOCK, "block $init");
+ writeU8(os, WASM_TYPE_NORESULT, "block type");
+
+ // Atomically check whether we win the race.
+ writeGetFlagAddress();
+ writeI32Const(os, 0, "expected flag value");
+ writeI32Const(os, 1, "new flag value");
+ writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
+ writeUleb128(os, WASM_OPCODE_I32_RMW_CMPXCHG, "i32.atomic.rmw.cmpxchg");
+ writeMemArg(os, 2, 0);
+
+ // Based on the value, decide what to do next.
+ writeU8(os, WASM_OPCODE_BR_TABLE, "br_table");
+ writeUleb128(os, 2, "label vector length");
+ writeUleb128(os, 0, "label $init");
+ writeUleb128(os, 1, "label $wait");
+ writeUleb128(os, 2, "default label $drop");
+
+ // Initialize passive data segments
+ writeU8(os, WASM_OPCODE_END, "end $init");
+ } else {
+ writeUleb128(os, 0, "num local decls");
+ }
+
for (const OutputSegment *s : segments) {
if (needsPassiveInitialization(s)) {
- // destination address
+ // For passive BSS segments we can simple issue a memory.fill(0).
+ // For non-BSS segments we do a memory.init. Both these
+ // instructions take as their first argument the destination
+ // address.
writePtrConst(os, s->startVA, is64, "destination address");
if (config->isPic) {
writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(),
- "memory_base");
+ "__memory_base");
writeU8(os, is64 ? WASM_OPCODE_I64_ADD : WASM_OPCODE_I32_ADD,
"i32.add");
}
- // source segment offset
- writeI32Const(os, 0, "segment offset");
- // memory region size
- writeI32Const(os, s->size, "memory region size");
- // memory.init instruction
- writeU8(os, WASM_OPCODE_MISC_PREFIX, "bulk-memory prefix");
- writeUleb128(os, WASM_OPCODE_MEMORY_INIT, "memory.init");
- writeUleb128(os, s->index, "segment index immediate");
- writeU8(os, 0, "memory index immediate");
+
+ // When we initialize the TLS segment we also set the `__tls_base`
+ // global. This allows the runtime to use this static copy of the
+ // TLS data for the first/main thread.
+ if (config->sharedMemory && s->isTLS()) {
+ if (config->isPic) {
+ // Cache the result of the addionion in local 0
+ writeU8(os, WASM_OPCODE_LOCAL_TEE, "local.tee");
+ writeUleb128(os, 1, "local 1");
+ } else {
+ writePtrConst(os, s->startVA, is64, "destination address");
+ }
+ writeU8(os, WASM_OPCODE_GLOBAL_SET, "GLOBAL_SET");
+ writeUleb128(os, WasmSym::tlsBase->getGlobalIndex(),
+ "__tls_base");
+ if (config->isPic) {
+ writeU8(os, WASM_OPCODE_LOCAL_GET, "local.tee");
+ writeUleb128(os, 1, "local 1");
+ }
+ }
+
+ if (s->isBss) {
+ writeI32Const(os, 0, "fill value");
+ writePtrConst(os, s->size, is64, "memory region size");
+ writeU8(os, WASM_OPCODE_MISC_PREFIX, "bulk-memory prefix");
+ writeUleb128(os, WASM_OPCODE_MEMORY_FILL, "memory.fill");
+ writeU8(os, 0, "memory index immediate");
+ } else {
+ writeI32Const(os, 0, "source segment offset");
+ writeI32Const(os, s->size, "memory region size");
+ writeU8(os, WASM_OPCODE_MISC_PREFIX, "bulk-memory prefix");
+ writeUleb128(os, WASM_OPCODE_MEMORY_INIT, "memory.init");
+ writeUleb128(os, s->index, "segment index immediate");
+ writeU8(os, 0, "memory index immediate");
+ }
}
}
- // Set flag to 2 to mark end of initialization
- writeGetFlagAddress();
- writeI32Const(os, 2, "flag value");
- writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
- writeUleb128(os, WASM_OPCODE_I32_ATOMIC_STORE, "i32.atomic.store");
- writeMemArg(os, 2, 0);
-
- // Notify any waiters that memory initialization is complete
- writeGetFlagAddress();
- writeI32Const(os, -1, "number of waiters");
- writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
- writeUleb128(os, WASM_OPCODE_ATOMIC_NOTIFY, "atomic.notify");
- writeMemArg(os, 2, 0);
- writeU8(os, WASM_OPCODE_DROP, "drop");
-
- writeU8(os, WASM_OPCODE_END, "END");
+ if (config->sharedMemory) {
+ // Set flag to 2 to mark end of initialization
+ writeGetFlagAddress();
+ writeI32Const(os, 2, "flag value");
+ writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
+ writeUleb128(os, WASM_OPCODE_I32_ATOMIC_STORE, "i32.atomic.store");
+ writeMemArg(os, 2, 0);
+
+ // Notify any waiters that memory initialization is complete
+ writeGetFlagAddress();
+ writeI32Const(os, -1, "number of waiters");
+ writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
+ writeUleb128(os, WASM_OPCODE_ATOMIC_NOTIFY, "atomic.notify");
+ writeMemArg(os, 2, 0);
+ writeU8(os, WASM_OPCODE_DROP, "drop");
+
+ // Branch to drop the segments
+ writeU8(os, WASM_OPCODE_BR, "br");
+ writeUleb128(os, 1, "label $drop");
+
+ // Wait for the winning thread to initialize memory
+ writeU8(os, WASM_OPCODE_END, "end $wait");
+ writeGetFlagAddress();
+ writeI32Const(os, 1, "expected flag value");
+ writeI64Const(os, -1, "timeout");
+
+ writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
+ writeUleb128(os, WASM_OPCODE_I32_ATOMIC_WAIT, "i32.atomic.wait");
+ writeMemArg(os, 2, 0);
+ writeU8(os, WASM_OPCODE_DROP, "drop");
+
+ // Unconditionally drop passive data segments
+ writeU8(os, WASM_OPCODE_END, "end $drop");
+ }
- // Unconditionally drop passive data segments
for (const OutputSegment *s : segments) {
- if (needsPassiveInitialization(s)) {
+ if (needsPassiveInitialization(s) && !s->isBss) {
+ // The TLS region should not be dropped since its is needed
+ // during the initialization of each thread (__wasm_init_tls).
+ if (config->sharedMemory && s->isTLS())
+ continue;
// data.drop instruction
writeU8(os, WASM_OPCODE_MISC_PREFIX, "bulk-memory prefix");
writeUleb128(os, WASM_OPCODE_DATA_DROP, "data.drop");
writeUleb128(os, s->index, "segment index immediate");
}
}
+
+ // End the function
writeU8(os, WASM_OPCODE_END, "END");
}
}
void Writer::createStartFunction() {
- if (WasmSym::startFunction) {
+ // If the start function exists when we have more than one function to call.
+ if (WasmSym::initMemory && WasmSym::applyGlobalRelocs) {
+ assert(WasmSym::startFunction);
std::string bodyContent;
{
raw_string_ostream os(bodyContent);
writeUleb128(os, 0, "num locals");
writeU8(os, WASM_OPCODE_CALL, "CALL");
- writeUleb128(os, WasmSym::initMemory->getFunctionIndex(),
+ writeUleb128(os, WasmSym::applyGlobalRelocs->getFunctionIndex(),
"function index");
writeU8(os, WASM_OPCODE_CALL, "CALL");
- writeUleb128(os, WasmSym::applyGlobalRelocs->getFunctionIndex(),
+ writeUleb128(os, WasmSym::initMemory->getFunctionIndex(),
"function index");
writeU8(os, WASM_OPCODE_END, "END");
}
// For -shared (PIC) output, we create create a synthetic function which will
// apply any relocations to the data segments on startup. This function is
-// called `__wasm_apply_data_relocs` and is added at the beginning of
-// `__wasm_call_ctors` before any of the constructors run.
+// called `__wasm_apply_data_relocs` and is expected to be called before
+// any user code (i.e. before `__wasm_call_ctors`).
void Writer::createApplyDataRelocationsFunction() {
LLVM_DEBUG(dbgs() << "createApplyDataRelocationsFunction\n");
// First write the body's contents to a string.
}
// Similar to createApplyDataRelocationsFunction but generates relocation code
-// fro WebAssembly globals. Because these globals are not shared between threads
+// for WebAssembly globals. Because these globals are not shared between threads
// these relocation need to run on every thread.
void Writer::createApplyGlobalRelocationsFunction() {
// First write the body's contents to a string.
{
raw_string_ostream os(bodyContent);
writeUleb128(os, 0, "num locals");
- out.globalSec->generateRelocationCode(os);
+ out.globalSec->generateRelocationCode(os, false);
writeU8(os, WASM_OPCODE_END, "END");
}
createFunction(WasmSym::applyGlobalRelocs, bodyContent);
}
+// Similar to createApplyGlobalRelocationsFunction but for
+// TLS symbols. This cannot be run during the start function
+// but must be delayed until __wasm_init_tls is called.
+void Writer::createApplyGlobalTLSRelocationsFunction() {
+ // First write the body's contents to a string.
+ std::string bodyContent;
+ {
+ raw_string_ostream os(bodyContent);
+ writeUleb128(os, 0, "num locals");
+ out.globalSec->generateRelocationCode(os, true);
+ writeU8(os, WASM_OPCODE_END, "END");
+ }
+
+ createFunction(WasmSym::applyGlobalTLSRelocs, bodyContent);
+}
+
// Create synthetic "__wasm_call_ctors" function based on ctor functions
// in input object.
void Writer::createCallCtorsFunction() {
- // If __wasm_call_ctors isn't referenced, there aren't any ctors, and we
- // aren't calling `__wasm_apply_data_relocs` for Emscripten-style PIC, don't
+ // If __wasm_call_ctors isn't referenced, there aren't any ctors, don't
// define the `__wasm_call_ctors` function.
- if (!WasmSym::callCtors->isLive() && !WasmSym::applyDataRelocs &&
- initFunctions.empty())
+ if (!WasmSym::callCtors->isLive() && initFunctions.empty())
return;
// First write the body's contents to a string.
raw_string_ostream os(bodyContent);
writeUleb128(os, 0, "num locals");
- if (WasmSym::applyDataRelocs) {
- writeU8(os, WASM_OPCODE_CALL, "CALL");
- writeUleb128(os, WasmSym::applyDataRelocs->getFunctionIndex(),
- "function index");
- }
-
// Call constructors
for (const WasmInitEntry &f : initFunctions) {
writeU8(os, WASM_OPCODE_CALL, "CALL");
writeUleb128(os, tlsSeg->index, "segment index immediate");
writeU8(os, 0, "memory index immediate");
}
+
+ if (WasmSym::applyGlobalTLSRelocs) {
+ writeU8(os, WASM_OPCODE_CALL, "CALL");
+ writeUleb128(os, WasmSym::applyGlobalTLSRelocs->getFunctionIndex(),
+ "function index");
+ }
writeU8(os, WASM_OPCODE_END, "end function");
}
}
void Writer::run() {
- if (config->relocatable || config->isPic)
- config->globalBase = 0;
-
// For PIC code the table base is assigned dynamically by the loader.
// For non-PIC, we start at 1 so that accessing table index 0 always traps.
if (!config->isPic) {
sym->forceExport = true;
}
- // Delay reporting error about explict exports until after addStartStopSymbols
- // which can create optional symbols.
+ // Delay reporting errors about explicit exports until after
+ // addStartStopSymbols which can create optional symbols.
for (auto &name : config->requiredExports) {
Symbol *sym = symtab->find(name);
if (!sym || !sym->isDefined()) {
}
}
- if (config->isPic && !config->sharedMemory) {
- // In shared memory mode all data segments are passive and initilized
+ log("-- populateTargetFeatures");
+ populateTargetFeatures();
+
+ // When outputting PIC code each segment lives at at fixes offset from the
+ // `__memory_base` import. Unless we support the extended const expression we
+ // can't do addition inside the constant expression, so we much combine the
+ // segments into a single one that can live at `__memory_base`.
+ if (config->isPic && !config->extendedConst && !config->sharedMemory) {
+ // In shared memory mode all data segments are passive and initialized
// via __wasm_init_memory.
log("-- combineOutputSegments");
combineOutputSegments();
createApplyDataRelocationsFunction();
if (WasmSym::applyGlobalRelocs)
createApplyGlobalRelocationsFunction();
+ if (WasmSym::applyGlobalTLSRelocs)
+ createApplyGlobalTLSRelocationsFunction();
if (WasmSym::initMemory)
createInitMemoryFunction();
createStartFunction();
}
}
- if (WasmSym::initTLS && WasmSym::initTLS->isLive())
+ if (WasmSym::initTLS && WasmSym::initTLS->isLive()) {
+ log("-- createInitTLSFunction");
createInitTLSFunction();
+ }
if (errorCount())
return;
calculateCustomSections();
log("-- populateSymtab");
populateSymtab();
- log("-- populateTargetFeatures");
- populateTargetFeatures();
+ log("-- checkImportExportTargetFeatures");
+ checkImportExportTargetFeatures();
log("-- addSections");
addSections();
log("Global Imports : " + Twine(out.importSec->getNumImportedGlobals()));
log("Tag Imports : " + Twine(out.importSec->getNumImportedTags()));
log("Table Imports : " + Twine(out.importSec->getNumImportedTables()));
- for (ObjFile *file : symtab->objectFiles)
- file->dumpInfo();
}
createHeader();
return;
if (Error e = buffer->commit())
- fatal("failed to write the output file: " + toString(std::move(e)));
+ fatal("failed to write output '" + buffer->getPath() +
+ "': " + toString(std::move(e)));
}
// Open a result file.
toString(static_cast<ValType>(type.Type));
}
-std::string toString(const WasmTagType &type) {
- if (type.Attribute == WASM_TAG_ATTRIBUTE_EXCEPTION)
- return "exception";
- return "unknown";
-}
-
static std::string toString(const llvm::wasm::WasmLimits &limits) {
std::string ret;
ret += "flags=0x" + std::to_string(limits.Flags);
}
namespace wasm {
+#ifdef LLVM_DEBUG
void debugWrite(uint64_t offset, const Twine &msg) {
LLVM_DEBUG(dbgs() << format(" | %08lld: ", offset) << msg << "\n");
}
+#endif
void writeUleb128(raw_ostream &os, uint64_t number, const Twine &msg) {
debugWrite(os.tell(), msg + "[" + utohexstr(number) + "]");
}
void writeInitExpr(raw_ostream &os, const WasmInitExpr &initExpr) {
+ assert(!initExpr.Extended);
+ writeInitExprMVP(os, initExpr.Inst);
+}
+
+void writeInitExprMVP(raw_ostream &os, const WasmInitExprMVP &initExpr) {
writeU8(os, initExpr.Opcode, "opcode");
switch (initExpr.Opcode) {
case WASM_OPCODE_I32_CONST:
writeU8(os, type.Mutable, "global mutable");
}
-void writeTagType(raw_ostream &os, const WasmTagType &type) {
- writeUleb128(os, type.Attribute, "tag attribute");
- writeUleb128(os, type.SigIndex, "sig index");
-}
-
-void writeTag(raw_ostream &os, const WasmTag &tag) {
- writeTagType(os, tag.Type);
-}
-
void writeTableType(raw_ostream &os, const WasmTableType &type) {
writeValueType(os, ValType(type.ElemType), "table type");
writeLimits(os, type.Limits);
writeGlobalType(os, import.Global);
break;
case WASM_EXTERNAL_TAG:
- writeTagType(os, import.Tag);
+ writeUleb128(os, 0, "tag attribute"); // Reserved "attribute" field
+ writeUleb128(os, import.SigIndex, "import sig index");
break;
case WASM_EXTERNAL_MEMORY:
writeLimits(os, import.Memory);
namespace lld {
namespace wasm {
+#ifdef LLVM_DEBUG
void debugWrite(uint64_t offset, const Twine &msg);
+#else
+#define debugWrite(...) (void *)0
+#endif
void writeUleb128(raw_ostream &os, uint64_t number, const Twine &msg);
void writeInitExpr(raw_ostream &os, const llvm::wasm::WasmInitExpr &initExpr);
+void writeInitExprMVP(raw_ostream &os,
+ const llvm::wasm::WasmInitExprMVP &initExpr);
+
void writeLimits(raw_ostream &os, const llvm::wasm::WasmLimits &limits);
void writeGlobalType(raw_ostream &os, const llvm::wasm::WasmGlobalType &type);
-void writeTagType(raw_ostream &os, const llvm::wasm::WasmTagType &type);
-
-void writeTag(raw_ostream &os, const llvm::wasm::WasmTag &tag);
-
void writeTableType(raw_ostream &os, const llvm::wasm::WasmTableType &type);
void writeImport(raw_ostream &os, const llvm::wasm::WasmImport &import);
std::string toString(llvm::wasm::ValType type);
std::string toString(const llvm::wasm::WasmSignature &sig);
std::string toString(const llvm::wasm::WasmGlobalType &type);
-std::string toString(const llvm::wasm::WasmTagType &type);
std::string toString(const llvm::wasm::WasmTableType &type);
} // namespace lld