1//===- lib/ReaderWriter/MachO/ShimPass.cpp -------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This linker pass updates branch-sites whose target is a different mode
10// (thumb vs arm).
11//
12// Arm code has two instruction encodings thumb and arm.  When branching from
13// one code encoding to another, you need to use an instruction that switches
14// the instruction mode.  Usually the transition only happens at call sites, and
15// the linker can transform a BL instruction in BLX (or vice versa).  But if the
16// compiler did a tail call optimization and a function ends with a branch (not
17// branch and link), there is no pc-rel BX instruction.
18//
19// The ShimPass looks for pc-rel B instructions that will need to switch mode.
20// For those cases it synthesizes a shim which does the transition, then
21// modifies the original atom with the B instruction to target to the shim atom.
22//
23//===----------------------------------------------------------------------===//
24
25#include "ArchHandler.h"
26#include "File.h"
27#include "MachOPasses.h"
28#include "lld/Common/LLVM.h"
29#include "lld/Core/DefinedAtom.h"
30#include "lld/Core/File.h"
31#include "lld/Core/Reference.h"
32#include "lld/Core/Simple.h"
33#include "lld/ReaderWriter/MachOLinkingContext.h"
34#include "llvm/ADT/DenseMap.h"
35#include "llvm/ADT/STLExtras.h"
36
37namespace lld {
38namespace mach_o {
39
40class ShimPass : public Pass {
41public:
42  ShimPass(const MachOLinkingContext &context)
43      : _ctx(context), _archHandler(_ctx.archHandler()),
44        _stubInfo(_archHandler.stubInfo()),
45        _file(*_ctx.make_file<MachOFile>("<mach-o shim pass>")) {
46    _file.setOrdinal(_ctx.getNextOrdinalAndIncrement());
47  }
48
49  llvm::Error perform(SimpleFile &mergedFile) override {
50    // Scan all references in all atoms.
51    for (const DefinedAtom *atom : mergedFile.defined()) {
52      for (const Reference *ref : *atom) {
53        // Look at non-call branches.
54        if (!_archHandler.isNonCallBranch(*ref))
55          continue;
56        const Atom *target = ref->target();
57        assert(target != nullptr);
58        if (const lld::DefinedAtom *daTarget = dyn_cast<DefinedAtom>(target)) {
59          bool atomIsThumb = _archHandler.isThumbFunction(*atom);
60          bool targetIsThumb = _archHandler.isThumbFunction(*daTarget);
61          if (atomIsThumb != targetIsThumb)
62            updateBranchToUseShim(atomIsThumb, *daTarget, ref);
63        }
64      }
65    }
66    // Exit early if no shims needed.
67    if (_targetToShim.empty())
68      return llvm::Error::success();
69
70    // Sort shim atoms so the layout order is stable.
71    std::vector<const DefinedAtom *> shims;
72    shims.reserve(_targetToShim.size());
73    for (auto element : _targetToShim) {
74      shims.push_back(element.second);
75    }
76    std::sort(shims.begin(), shims.end(),
77              [](const DefinedAtom *l, const DefinedAtom *r) {
78                return (l->name() < r->name());
79              });
80
81    // Add all shims to master file.
82    for (const DefinedAtom *shim : shims)
83      mergedFile.addAtom(*shim);
84
85    return llvm::Error::success();
86  }
87
88private:
89
90  void updateBranchToUseShim(bool thumbToArm, const DefinedAtom& target,
91                             const Reference *ref) {
92    // Make file-format specific stub and other support atoms.
93    const DefinedAtom *shim = this->getShim(thumbToArm, target);
94    assert(shim != nullptr);
95    // Switch branch site to target shim atom.
96    const_cast<Reference *>(ref)->setTarget(shim);
97  }
98
99  const DefinedAtom* getShim(bool thumbToArm, const DefinedAtom& target) {
100    auto pos = _targetToShim.find(&target);
101    if ( pos != _targetToShim.end() ) {
102      // Reuse an existing shim.
103      assert(pos->second != nullptr);
104      return pos->second;
105    } else {
106      // There is no existing shim, so create a new one.
107      const DefinedAtom *shim = _archHandler.createShim(_file, thumbToArm,
108                                                        target);
109       _targetToShim[&target] = shim;
110       return shim;
111    }
112  }
113
114  const MachOLinkingContext &_ctx;
115  mach_o::ArchHandler                            &_archHandler;
116  const ArchHandler::StubInfo                    &_stubInfo;
117  MachOFile                                      &_file;
118  llvm::DenseMap<const Atom*, const DefinedAtom*> _targetToShim;
119};
120
121
122
123void addShimPass(PassManager &pm, const MachOLinkingContext &ctx) {
124  pm.add(std::make_unique<ShimPass>(ctx));
125}
126
127} // end namespace mach_o
128} // end namespace lld
129