xref: /llvm-project/bolt/docs/RuntimeLibrary.md (revision 4c106cfdf7cf7eec861ad3983a3dd9a9e8f3a8ae)
1*1c5d3a05SAmir Ayupov# BOLT ORC-based linker
2*1c5d3a05SAmir Ayupov
3*1c5d3a05SAmir AyupovA high-level view on the simple linker used to insert auxiliary/library code into the final binary produced by BOLT. This is built on top of LLVM's ORC infra (the newest iteration on JITting for LLVM).
4*1c5d3a05SAmir Ayupov
5*1c5d3a05SAmir Ayupov## Several levels of code injection
6*1c5d3a05SAmir Ayupov
7*1c5d3a05SAmir AyupovWhen BOLT starts processing an input executable, its first task is to raise the binary to a low-level IR with CFG. After this is done, we are ready to change code in this binary. Throughout BOLT's pipeline of code transformations, there are plenty of situations when we need to insert new code or fix existing code.
8*1c5d3a05SAmir Ayupov
9*1c5d3a05SAmir AyupovIf operating with small code changes inside a basic block, we typically defer this work to MCPlusBuilder. This is our target-independent interface to create new instructions, but it also contains some functions that may create code spanning multiple basic blocks (for instance, when doing indirect call promotion and unrolling an indirect call into a ladder of comparisons/direct calls). The implementation here usually boils down to programmatically creating new MCInst instructions while setting their opcodes according to the target list (see X86GenInstOpcodes.inc generated by tablegen in an LLVM build).
10*1c5d3a05SAmir Ayupov
11*1c5d3a05SAmir AyupovHowever, this approach quickly becomes awkward if we want to insert a lot of code, especially if this code is frozen and never changes. In these situations, it is more convenient to have a runtime library with all the code you need to insert. This library defines some symbols and can be linked into the final binary. In this case, all you need to do in a BOLT transformation is to insert a call to your library.
12*1c5d3a05SAmir Ayupov
13*1c5d3a05SAmir Ayupov## The runtime library
14*1c5d3a05SAmir Ayupov
15*1c5d3a05SAmir AyupovCurrently, our runtime library is written in C++ and contains code that helps us instrument a binary.
16*1c5d3a05SAmir Ayupov
17*1c5d3a05SAmir Ayupov### Limitations
18*1c5d3a05SAmir AyupovOur library is not written with regular C++ code as it is not linked against any other libraries (this means we cannnot rely on anything defined on libstdc++, glibc, libgcc etc), but is self sufficient. In runtime/CMakeLists.txt, we can see it is built with -ffreestanding, which requires the compiler to avoid using a runtime library by itself.
19*1c5d3a05SAmir Ayupov
20*1c5d3a05SAmir AyupovWhile this requires us to make our own syscalls, it does simplify our linker a lot, which is very limited and can only do basic function name resolving. However, this is a big improvement in comparison with programmatically generating the code in assembly language using MCInsts.
21*1c5d3a05SAmir Ayupov
22*1c5d3a05SAmir AyupovA few more quirks:
23*1c5d3a05SAmir Ayupov
24*1c5d3a05SAmir Ayupov* No BSS section: don't use uninitialized globals
25*1c5d3a05SAmir Ayupov* No dependencies on foreign code: self sufficient
26*1c5d3a05SAmir Ayupov* You should closely watch the generated bolt_rt object files, anything requiring fancy linker features will break. We only support bare bones .text, .data and nothing else.
27*1c5d3a05SAmir Ayupov
28*1c5d3a05SAmir AyupovRead instr.cpp opening comment for more details.
29*1c5d3a05SAmir Ayupov
30*1c5d3a05SAmir Ayupov
31*1c5d3a05SAmir Ayupov## Linking
32*1c5d3a05SAmir Ayupov
33*1c5d3a05SAmir AyupovWhile RewriteInstance::emitAndLink() will perform an initial link step to resolve all references of the input program, it will not start linking the runtime library right away. The input program lives in its own module that may end up with unresolved references to the runtime library.
34*1c5d3a05SAmir Ayupov
35*1c5d3a05SAmir AyupovRewriteInstance::linkRuntime() has the job of actually reading individual .o files and adding them to the binary. We currently have a single .o file, so after it is read, ORC can finally resolve references from the first module to the newly inserted .o objects.
36*1c5d3a05SAmir Ayupov
37*1c5d3a05SAmir AyupovThis sequence of steps is done by calls to addObject() and emitAndFinalize(). The latter will trigger symbol resolution, relying on the symbol resolver provided by us when calling createLegacyLookupResolver().
38