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 
37 namespace lld {
38 namespace mach_o {
39 
40 class ShimPass : public Pass {
41 public:
ShimPass(const MachOLinkingContext & context)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 
perform(SimpleFile & mergedFile)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 
88 private:
89 
updateBranchToUseShim(bool thumbToArm,const DefinedAtom & target,const Reference * ref)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 
getShim(bool thumbToArm,const DefinedAtom & target)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 
addShimPass(PassManager & pm,const MachOLinkingContext & ctx)123 void 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