| # JITLink and ORC's ObjectLinkingLayer |
| |
| |
| ## Introduction |
| |
| This document aims to provide a high-level overview of the design and API |
| of the JITLink library. It assumes some familiarity with linking and |
| relocatable object files, but should not require deep expertise. If you know |
| what a section, symbol, and relocation are then you should find this document |
| accessible. If it is not, please submit a patch ({doc}`Contributing`) or file a |
| bug ({doc}`HowToSubmitABug`). |
| |
| JITLink is a library for {ref}`jit_linking`. It was built to support the |
| {doc}`ORC JIT APIs <ORCv2>` and is most commonly accessed via ORC's |
| ObjectLinkingLayer API. JITLink was developed with the aim of supporting the |
| full set of features provided by each object format; including static |
| initializers, exception handling, thread local variables, and language runtime |
| registration. Supporting these features enables |
| ORC to execute code generated from source languages which rely on these features |
| (e.g. C++ requires object format support for static initializers to support |
| static constructors, eh-frame registration for exceptions, and TLV support for |
| thread locals; Swift and Objective-C require language runtime registration for |
| many features). For some object format features support is provided entirely |
| within JITLink, and for others it is provided in cooperation with the |
| (prototype) ORC runtime. |
| |
| JITLink aims to support the following features, some of which are still under |
| development: |
| |
| 1. Cross-process and cross-architecture linking of single relocatable objects |
| into a target *executor* process. |
| 2. Support for all object format features. |
| 3. Open linker data structures (`LinkGraph`) and pass system. |
| |
| ## JITLink and ObjectLinkingLayer |
| |
| `ObjectLinkingLayer` is ORCs wrapper for JITLink. It is an ORC layer that |
| allows objects to be added to a `JITDylib`, or emitted from some higher level |
| program representation. When an object is emitted, `ObjectLinkingLayer` uses |
| JITLink to construct a `LinkGraph` (see {ref}`constructing_linkgraphs`) and |
| calls JITLink's `link` function to link the graph into the executor process. |
| |
| The `ObjectLinkingLayer` class provides a plugin API, |
| `ObjectLinkingLayer::Plugin`, which users can subclass in order to inspect and |
| modify `LinkGraph` instances at link time, and react to important JIT events |
| (such as an object being emitted into target memory). This enables many features |
| and optimizations that were not possible under MCJIT or RuntimeDyld. |
| |
| ### ObjectLinkingLayer Plugins |
| |
| The `ObjectLinkingLayer::Plugin` class provides the following methods: |
| |
| - `modifyPassConfig` is called each time a LinkGraph is about to be linked. It |
| can be overridden to install JITLink *Passes* to run during the link process. |
| |
| ```c++ |
| void modifyPassConfig(MaterializationResponsibility &MR, |
| jitlink::LinkGraph &G, |
| jitlink::PassConfiguration &Config) |
| ``` |
| |
| - `notifyLoaded` is called before the link begins, and can be overridden to |
| set up any initial state for the given `MaterializationResponsibility` if |
| needed. |
| |
| ```c++ |
| void notifyLoaded(MaterializationResponsibility &MR) |
| ``` |
| |
| - `notifyEmitted` is called after the link is complete and code has been |
| emitted to the executor process. It can be overridden to finalize state |
| for the `MaterializationResponsibility` if needed. |
| |
| ```c++ |
| Error notifyEmitted(MaterializationResponsibility &MR) |
| ``` |
| |
| - `notifyFailed` is called if the link fails at any point. It can be |
| overridden to react to the failure (e.g. to deallocate any already allocated |
| resources). |
| |
| ```c++ |
| Error notifyFailed(MaterializationResponsibility &MR) |
| ``` |
| |
| - `notifyRemovingResources` is called when a request is made to remove any |
| resources associated with the `ResourceKey` *K* for the |
| `MaterializationResponsibility`. |
| |
| ```c++ |
| Error notifyRemovingResources(JITDylib &JD, ResourceKey K) |
| ``` |
| |
| - `notifyTransferringResources` is called if/when a request is made to |
| transfer tracking of any resources associated with `ResourceKey` |
| *SrcKey* to *DstKey*. |
| |
| ```c++ |
| void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, |
| ResourceKey SrcKey) |
| ``` |
| |
| Plugin authors are required to implement the `notifyFailed`, |
| `notifyRemovingResources`, and `notifyTransferringResources` methods in |
| order to safely manage resources in the case of resource removal or transfer, |
| or link failure. If no resources are managed by the plugin then these methods |
| can be implemented as no-ops returning `Error::success()`. |
| |
| Plugin instances are added to an `ObjectLinkingLayer` by |
| calling the `addPlugin` method [^1]. E.g. |
| |
| ```c++ |
| // Plugin class to print the set of defined symbols in an object when that |
| // object is linked. |
| class MyPlugin : public ObjectLinkingLayer::Plugin { |
| public: |
| |
| // Add passes to print the set of defined symbols after dead-stripping. |
| void modifyPassConfig(MaterializationResponsibility &MR, |
| jitlink::LinkGraph &G, |
| jitlink::PassConfiguration &Config) override { |
| Config.PostPrunePasses.push_back([this](jitlink::LinkGraph &G) { |
| return printAllSymbols(G); |
| }); |
| } |
| |
| // Implement mandatory overrides: |
| Error notifyFailed(MaterializationResponsibility &MR) override { |
| return Error::success(); |
| } |
| Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { |
| return Error::success(); |
| } |
| void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, |
| ResourceKey SrcKey) override {} |
| |
| // JITLink pass to print all defined symbols in G. |
| Error printAllSymbols(LinkGraph &G) { |
| for (auto *Sym : G.defined_symbols()) |
| if (Sym->hasName()) |
| dbgs() << Sym->getName() << "\n"; |
| return Error::success(); |
| } |
| }; |
| |
| // Create our LLJIT instance using a custom object linking layer setup. |
| // This gives us a chance to install our plugin. |
| auto J = ExitOnErr(LLJITBuilder() |
| .setObjectLinkingLayerCreator( |
| [](ExecutionSession &ES, const Triple &T) { |
| // Manually set up the ObjectLinkingLayer for our LLJIT |
| // instance. |
| auto OLL = std::make_unique<ObjectLinkingLayer>( |
| ES, std::make_unique<jitlink::InProcessMemoryManager>()); |
| |
| // Install our plugin: |
| OLL->addPlugin(std::make_unique<MyPlugin>()); |
| |
| return OLL; |
| }) |
| .create()); |
| |
| // Add an object to the JIT. Nothing happens here: linking isn't triggered |
| // until we look up some symbol in our object. |
| ExitOnErr(J->addObject(loadFromDisk("main.o"))); |
| |
| // Plugin triggers here when our lookup of main triggers linking of main.o |
| auto MainSym = J->lookup("main"); |
| ``` |
| |
| ## LinkGraph |
| |
| JITLink maps all relocatable object formats to a generic `LinkGraph` type |
| that is designed to make linking fast and easy (`LinkGraph` instances can |
| also be created manually. See {ref}`constructing_linkgraphs`). |
| |
| Relocatable object formats (e.g. COFF, ELF, MachO) differ in their details, |
| but share a common goal: to represent machine level code and data with |
| annotations that allow them to be relocated in a virtual address space. To |
| this end they usually contain names (symbols) for content defined inside the |
| file or externally, chunks of content that must be moved as a unit (sections |
| or subsections, depending on the format), and annotations describing how to |
| patch content based on the final address of some target symbol/section |
| (relocations). |
| |
| At a high level, the `LinkGraph` type represents these concepts as a decorated |
| graph. Nodes in the graph represent symbols and content, and edges represent |
| relocations. Each of the elements of the graph is listed here: |
| |
| - `Addressable` -- A node in the link graph that can be assigned an address |
| in the executor process's virtual address space. |
| |
| Absolute and external symbols are represented using plain `Addressable` |
| instances. Content defined inside the object file is represented using the |
| `Block` subclass. |
| |
| - `Block` -- An `Addressable` node that has `Content` (or is marked as |
| zero-filled), a parent `Section`, a `Size`, an `Alignment` (and an |
| `AlignmentOffset`), and a list of `Edge` instances. |
| |
| Blocks provide a container for binary content which must remain contiguous in |
| the target address space (a *layout unit*). Many interesting low level |
| operations on `LinkGraph` instances involve inspecting or mutating block |
| content or edges. |
| |
| - `Content` is represented as an `llvm::StringRef`, and accessible via |
| the `getContent` method. Content is only available for content blocks, |
| and not for zero-fill blocks (use `isZeroFill` to check, and prefer |
| `getSize` when only the block size is needed as it works for both |
| zero-fill and content blocks). |
| - `Section` is represented as a `Section&` reference, and accessible via |
| the `getSection` method. The `Section` class is described in more detail |
| below. |
| - `Size` is represented as a `size_t`, and is accessible via the |
| `getSize` method for both content and zero-filled blocks. |
| - `Alignment` is represented as a `uint64_t`, and available via the |
| `getAlignment` method. It represents the minimum alignment requirement (in |
| bytes) of the start of the block. |
| - `AlignmentOffset` is represented as a `uint64_t`, and accessible via the |
| `getAlignmentOffset` method. It represents the offset from the alignment |
| required for the start of the block. This is required to support blocks |
| whose minimum alignment requirement comes from data at some non-zero offset |
| inside the block. E.g. if a block consists of a single byte (with byte |
| alignment) followed by a uint64_t (with 8-byte alignment), then the block |
| will have 8-byte alignment with an alignment offset of 7. |
| - list of `Edge` instances. An iterator range for this list is returned by |
| the `edges` method. The `Edge` class is described in more detail below. |
| |
| - `Symbol` -- An offset from an `Addressable` (often a `Block`), with an |
| optional `Name`, a `Linkage`, a `Scope`, a `Callable` flag, and a |
| `Live` flag. |
| |
| Symbols make it possible to name content (blocks and addressables are |
| anonymous), or target content with an `Edge`. |
| |
| - `Name` is represented as an `llvm::StringRef` (equal to |
| `llvm::StringRef()` if the symbol has no name), and accessible via the |
| `getName` method. |
| - `Linkage` is one of *Strong* or *Weak*, and is accessible via the |
| `getLinkage` method. The `JITLinkContext` can use this flag to determine |
| whether this symbol definition should be kept or dropped. |
| - `Scope` is one of *Default*, *Hidden*, or *Local*, and is accessible via |
| the `getScope` method. The `JITLinkContext` can use this to determine |
| who should be able to see the symbol. A symbol with default scope should be |
| globally visible. A symbol with hidden scope should be visible to other |
| definitions within the same simulated dylib (e.g. ORC `JITDylib`) or |
| executable, but not from elsewhere. A symbol with local scope should only be |
| visible within the current `LinkGraph`. |
| - `Callable` is a boolean which is set to true if this symbol can be called, |
| and is accessible via the `isCallable` method. This can be used to |
| automate the introduction of call-stubs for lazy compilation. |
| - `Live` is a boolean that can be set to mark this symbol as root for |
| dead-stripping purposes (see {ref}`generic_link_algorithm`). JITLink's |
| dead-stripping algorithm will propagate liveness flags through the graph to |
| all reachable symbols before deleting any symbols (and blocks) that are not |
| marked live. |
| |
| - `Edge` -- A quad of an `Offset` (implicitly from the start of the |
| containing `Block`), a `Kind` (describing the relocation type), a |
| `Target`, and an `Addend`. |
| |
| Edges represent relocations, and occasionally other relationships, between |
| blocks and symbols. |
| |
| - `Offset`, accessible via `getOffset`, is an offset from the start of the |
| `Block` containing the `Edge`. |
| - `Kind`, accessible via `getKind` is a relocation type -- it describes |
| what kinds of changes (if any) should be made to block content at the given |
| `Offset` based on the address of the `Target`. |
| - `Target`, accessible via `getTarget`, is a pointer to a `Symbol`, |
| representing whose address is relevant to the fixup calculation specified by |
| the edge's `Kind`. |
| - `Addend`, accessible via `getAddend`, is a constant whose interpretation |
| is determined by the edge's `Kind`. |
| |
| - `Section` -- A set of `Symbol` instances, plus a set of `Block` |
| instances, with a `Name`, a set of `ProtectionFlags`, and an `Ordinal`. |
| |
| Sections make it easy to iterate over the symbols or blocks associated with |
| a particular section in the source object file. |
| |
| - `blocks()` returns an iterator over the set of blocks defined in the |
| section (as `Block*` pointers). |
| - `symbols()` returns an iterator over the set of symbols defined in the |
| section (as `Symbol*` pointers). |
| - `Name` is represented as an `llvm::StringRef`, and is accessible via the |
| `getName` method. |
| - `ProtectionFlags` are represented as a sys::Memory::ProtectionFlags enum, |
| and accessible via the `getProtectionFlags` method. These flags describe |
| whether the section is readable, writable, executable, or some combination |
| of these. The most common combinations are `RW-` for writable data, |
| `R--` for constant data, and `R-X` for code. |
| - `SectionOrdinal`, accessible via `getOrdinal`, is a number used to order |
| the section relative to others. It is usually used to preserve section |
| order within a segment (a set of sections with the same memory protections) |
| when laying out memory. |
| |
| For the graph-theorists: The `LinkGraph` is bipartite, with one set of |
| `Symbol` nodes and one set of `Addressable` nodes. Each `Symbol` node has |
| one (implicit) edge to its target `Addressable`. Each `Block` has a set of |
| edges (possibly empty, represented as `Edge` instances) back to elements of |
| the `Symbol` set. For convenience and performance of common algorithms, |
| symbols and blocks are further grouped into `Sections`. |
| |
| The `LinkGraph` itself provides operations for constructing, removing, and |
| iterating over sections, symbols, and blocks. It also provides metadata |
| and utilities relevant to the linking process: |
| |
| - Graph element operations |
| |
| - `sections` returns an iterator over all sections in the graph. |
| - `findSectionByName` returns a pointer to the section with the given |
| name (as a `Section*`) if it exists, otherwise returns a nullptr. |
| - `blocks` returns an iterator over all blocks in the graph (across all |
| sections). |
| - `defined_symbols` returns an iterator over all defined symbols in the |
| graph (across all sections). |
| - `external_symbols` returns an iterator over all external symbols in the |
| graph. |
| - `absolute_symbols` returns an iterator over all absolute symbols in the |
| graph. |
| - `createSection` creates a section with a given name and protection flags. |
| - `createContentBlock` creates a block with the given initial content, |
| parent section, address, alignment, and alignment offset. |
| - `createZeroFillBlock` creates a zero-fill block with the given size, |
| parent section, address, alignment, and alignment offset. |
| - `addExternalSymbol` creates a new addressable and symbol with a given |
| name, size, and linkage. |
| - `addAbsoluteSymbol` creates a new addressable and symbol with a given |
| name, address, size, linkage, scope, and liveness. |
| - `addCommonSymbol` convenience function for creating a zero-filled block |
| and weak symbol with a given name, scope, section, initial address, size, |
| alignment and liveness. |
| - `addAnonymousSymbol` creates a new anonymous symbol for a given block, |
| offset, size, callable-ness, and liveness. |
| - `addDefinedSymbol` creates a new symbol for a given block with a name, |
| offset, size, linkage, scope, callable-ness and liveness. |
| - `makeExternal` transforms a formerly defined symbol into an external one |
| by creating a new addressable and pointing the symbol at it. The existing |
| block is not deleted, but can be manually removed (if unreferenced) by |
| calling `removeBlock`. All edges to the symbol remain valid, but the |
| symbol must now be defined outside this `LinkGraph`. |
| - `removeExternalSymbol` removes an external symbol and its target |
| addressable. The target addressable must not be referenced by any other |
| symbols. |
| - `removeAbsoluteSymbol` removes an absolute symbol and its target |
| addressable. The target addressable must not be referenced by any other |
| symbols. |
| - `removeDefinedSymbol` removes a defined symbol, but *does not* remove |
| its target block. |
| - `removeBlock` removes the given block. |
| - `splitBlock` split a given block in two at a given index (useful where |
| it is known that a block contains decomposable records, e.g. CFI records |
| in an eh-frame section). |
| |
| - Graph utility operations |
| |
| - `getName` returns the name of this graph, which is usually based on the |
| name of the input object file. |
| - `getTargetTriple` returns an `llvm::Triple` for the executor process. |
| - `getPointerSize` returns the size of a pointer (in bytes) in the executor |
| process. |
| - `getEndianness` returns the endianness of the executor process. |
| - `allocateString` copies data from a given `llvm::Twine` into the |
| link graph's internal allocator. This can be used to ensure that content |
| created inside a pass outlives that pass's execution. |
| |
| (generic_link_algorithm)= |
| |
| ## Generic Link Algorithm |
| |
| JITLink provides a generic link algorithm which can be extended / modified at |
| certain points by the introduction of JITLink {ref}`passes`. |
| |
| At the end of each phase the linker packages its state into a *continuation* |
| and calls the `JITLinkContext` object to perform a (potentially high-latency) |
| asynchronous operation: allocating memory, resolving external symbols, and |
| finally transferring linked memory to the executing process. |
| |
| 1. Phase 1 |
| |
| This phase is called immediately by the `link` function as soon as the |
| initial configuration (including the pass pipeline setup) is complete. |
| |
| 1. Run pre-prune passes. |
| |
| These passes are called on the graph before it is pruned. At this stage |
| `LinkGraph` nodes still have their original vmaddrs. A mark-live pass |
| (supplied by the `JITLinkContext`) will be run at the end of this |
| sequence to mark the initial set of live symbols. |
| |
| Notable use cases: marking nodes live, accessing/copying graph data that |
| will be pruned (e.g. metadata that's important for the JIT, but not needed |
| for the link process). |
| |
| 2. Prune (dead-strip) the `LinkGraph`. |
| |
| Removes all symbols and blocks not reachable from the initial set of live |
| symbols. |
| |
| This allows JITLink to remove unreachable symbols / content, including |
| overridden weak and redundant ODR definitions. |
| |
| 3. Run post-prune passes. |
| |
| These passes are run on the graph after dead-stripping, but before memory |
| is allocated or nodes assigned their final target vmaddrs. |
| |
| Passes run at this stage benefit from pruning, as dead functions and data |
| have been stripped from the graph. However new content can still be added |
| to the graph, as target and working memory have not been allocated yet. |
| |
| Notable use cases: Building Global Offset Table (GOT), Procedure Linkage |
| Table (PLT), and Thread Local Variable (TLV) entries. |
| |
| 4. Asynchronously allocate memory. |
| |
| Calls the `JITLinkContext`'s `JITLinkMemoryManager` to allocate both |
| working and target memory for the graph. As part of this process the |
| `JITLinkMemoryManager` will update the addresses of all nodes |
| defined in the graph to their assigned target address. |
| |
| Note: This step only updates the addresses of nodes defined in this graph. |
| External symbols will still have null addresses. |
| |
| 2. Phase 2 |
| |
| 1. Run post-allocation passes. |
| |
| These passes are run on the graph after working and target memory have |
| been allocated, but before the `JITLinkContext` is notified of the |
| final addresses of the symbols in the graph. This gives these passes a |
| chance to set up data structures associated with target addresses before |
| any JITLink clients (especially ORC queries for symbol resolution) can |
| attempt to access them. |
| |
| Notable use cases: Setting up mappings between target addresses and |
| JIT data structures, such as a mapping between `__dso_handle` and |
| `JITDylib*`. |
| |
| 2. Notify the `JITLinkContext` of the assigned symbol addresses. |
| |
| Calls `JITLinkContext::notifyResolved` on the link graph, allowing |
| clients to react to the symbol address assignments made for this graph. |
| In ORC this is used to notify any pending queries for *resolved* symbols, |
| including pending queries from concurrently running JITLink instances that |
| have reached the next step and are waiting on the address of a symbol in |
| this graph to proceed with their link. |
| |
| 3. Identify external symbols and resolve their addresses asynchronously. |
| |
| Calls the `JITLinkContext` to resolve the target address of any external |
| symbols in the graph. |
| |
| 3. Phase 3 |
| |
| 1. Apply external symbol resolution results. |
| |
| This updates the addresses of all external symbols. At this point all |
| nodes in the graph have their final target addresses, however node |
| content still points back to the original data in the object file. |
| |
| 2. Run pre-fixup passes. |
| |
| These passes are called on the graph after all nodes have been assigned |
| their final target addresses, but before node content is copied into |
| working memory and fixed up. Passes run at this stage can make late |
| optimizations to the graph and content based on address layout. |
| |
| Notable use cases: GOT and PLT relaxation, where GOT and PLT accesses are |
| bypassed for fixup targets that are directly accessible under the assigned |
| memory layout. |
| |
| 3. Copy block content to working memory and apply fixups. |
| |
| Copies all block content into allocated working memory (following the |
| target layout) and applies fixups. Graph blocks are updated to point at |
| the fixed up content. |
| |
| 4. Run post-fixup passes. |
| |
| These passes are called on the graph after fixups have been applied and |
| blocks updated to point to the fixed up content. |
| |
| Post-fixup passes can inspect blocks contents to see the exact bytes that |
| will be copied to the assigned target addresses. |
| |
| 5. Finalize memory asynchronously. |
| |
| Calls the `JITLinkMemoryManager` to copy working memory to the executor |
| process and apply the requested permissions. |
| |
| 4. Phase 3. |
| |
| 1. Notify the context that the graph has been emitted. |
| |
| Calls `JITLinkContext::notifyFinalized` and hands off the |
| `JITLinkMemoryManager::FinalizedAlloc` object for this graph's memory |
| allocation. This allows the context to track/hold memory allocations and |
| react to the newly emitted definitions. In ORC this is used to update the |
| `ExecutionSession` instance's dependence graph, which may result in |
| these symbols (and possibly others) becoming *Ready* if all of their |
| dependencies have also been emitted. |
| |
| (passes)= |
| |
| ### Passes |
| |
| JITLink passes are `std::function<Error(LinkGraph&)>` instances. They are free |
| to inspect and modify the given `LinkGraph` subject to the constraints of |
| whatever phase they are running in (see {ref}`generic_link_algorithm`). If a |
| pass returns `Error::success()` then linking continues. If a pass returns |
| a failure value then linking is stopped and the `JITLinkContext` is notified |
| that the link failed. |
| |
| Passes may be used by both JITLink backends (e.g. MachO/x86-64 implements GOT |
| and PLT construction as a pass), and external clients like |
| `ObjectLinkingLayer::Plugin`. |
| |
| In combination with the open `LinkGraph` API, JITLink passes enable the |
| implementation of powerful new features. For example: |
| |
| - Relaxation optimizations -- A pre-fixup pass can inspect GOT accesses and PLT |
| calls and identify situations where the addresses of the entry target and the |
| access are close enough to be accessed directly. In this case the pass can |
| rewrite the instruction stream of the containing block and update the fixup |
| edges to make the access direct. |
| |
| Code for this looks like: |
| |
| ```c++ |
| Error relaxGOTEdges(LinkGraph &G) { |
| for (auto *B : G.blocks()) |
| for (auto &E : B->edges()) |
| if (E.getKind() == x86_64::GOTLoad) { |
| auto &GOTTarget = getGOTEntryTarget(E.getTarget()); |
| if (isInRange(B.getFixupAddress(E), GOTTarget)) { |
| // Rewrite B.getContent() at fixup address from |
| // MOVQ to LEAQ |
| |
| // Update edge target and kind. |
| E.setTarget(GOTTarget); |
| E.setKind(x86_64::PCRel32); |
| } |
| } |
| |
| return Error::success(); |
| } |
| ``` |
| |
| - Metadata registration -- Post allocation passes can be used to record the |
| address range of sections in the target. This can be used to register the |
| metadata (e.g exception handling frames, language metadata) in the target |
| once memory has been finalized. |
| |
| ```c++ |
| Error registerEHFrameSection(LinkGraph &G) { |
| if (auto *Sec = G.findSectionByName("__eh_frame")) { |
| SectionRange SR(*Sec); |
| registerEHFrameSection(SR.getStart(), SR.getEnd()); |
| } |
| |
| return Error::success(); |
| } |
| ``` |
| |
| - Record call sites for later mutation -- A post-allocation pass can record |
| the call sites of all calls to a particular function, allowing those call |
| sites to be updated later at runtime (e.g. for instrumentation, or to |
| enable the function to be lazily compiled but still called directly after |
| compilation). |
| |
| ```c++ |
| StringRef FunctionName = "foo"; |
| std::vector<ExecutorAddr> CallSitesForFunction; |
| |
| auto RecordCallSites = |
| [&](LinkGraph &G) -> Error { |
| for (auto *B : G.blocks()) |
| for (auto &E : B.edges()) |
| if (E.getKind() == CallEdgeKind && |
| E.getTarget().hasName() && |
| E.getTraget().getName() == FunctionName) |
| CallSitesForFunction.push_back(B.getFixupAddress(E)); |
| return Error::success(); |
| }; |
| ``` |
| |
| ### Memory Management with JITLinkMemoryManager |
| |
| JIT linking requires allocation of two kinds of memory: working memory in the |
| JIT process and target memory in the execution process (these processes and |
| memory allocations may be one and the same, depending on how the user wants |
| to build their JIT). It also requires that these allocations conform to the |
| requested code model in the target process (e.g. MachO/x86-64's Small code |
| model requires that all code and data for a simulated dylib is allocated within |
| 4Gb). Finally, it is natural to make the memory manager responsible for |
| transferring memory to the target address space and applying memory protections, |
| since the memory manager must know how to communicate with the executor, and |
| since sharing and protection assignment can often be efficiently managed (in |
| the common case of running across processes on the same machine for security) |
| via the host operating system's virtual memory management APIs. |
| |
| To satisfy these requirements `JITLinkMemoryManager` adopts the following |
| design: The memory manager itself has just two virtual methods for asynchronous |
| operations (each with convenience overloads for calling synchronously): |
| |
| ```c++ |
| /// Called when allocation has been completed. |
| using OnAllocatedFunction = |
| unique_function<void(Expected<std::unique_ptr<InFlightAlloc>)>; |
| |
| /// Called when deallocation has completed. |
| using OnDeallocatedFunction = unique_function<void(Error)>; |
| |
| /// Call to allocate memory. |
| virtual void allocate(const JITLinkDylib *JD, LinkGraph &G, |
| OnAllocatedFunction OnAllocated) = 0; |
| |
| /// Call to deallocate memory. |
| virtual void deallocate(std::vector<FinalizedAlloc> Allocs, |
| OnDeallocatedFunction OnDeallocated) = 0; |
| ``` |
| |
| The `allocate` method takes a `JITLinkDylib*` representing the target |
| simulated dylib, a reference to the `LinkGraph` that must be allocated for, |
| and a callback to run once an `InFlightAlloc` has been constructed. |
| `JITLinkMemoryManager` implementations can (optionally) use the `JD` |
| argument to manage a per-simulated-dylib memory pool (since code model |
| constraints are typically imposed on a per-dylib basis, and not across |
| dylibs) [^2]. The `LinkGraph` describes the object file that we need to |
| allocate memory for. The allocator must allocate working memory for all of |
| the Blocks defined in the graph, assign address space for each Block within the |
| executing processes memory, and update the Blocks' addresses to reflect this |
| assignment. Block content should be copied to working memory, but does not need |
| to be transferred to executor memory yet (that will be done once the content is |
| fixed up). `JITLinkMemoryManager` implementations can take full |
| responsibility for these steps, or use the `BasicLayout` utility to reduce |
| the task to allocating working and executor memory for *segments*: chunks of |
| memory defined by permissions, alignments, content sizes, and zero-fill sizes. |
| Once the allocation step is complete the memory manager should construct an |
| `InFlightAlloc` object to represent the allocation, and then pass this object |
| to the `OnAllocated` callback. |
| |
| The `InFlightAlloc` object has two virtual methods: |
| |
| ```c++ |
| using OnFinalizedFunction = unique_function<void(Expected<FinalizedAlloc>)>; |
| using OnAbandonedFunction = unique_function<void(Error)>; |
| |
| /// Called prior to finalization if the allocation should be abandoned. |
| virtual void abandon(OnAbandonedFunction OnAbandoned) = 0; |
| |
| /// Called to transfer working memory to the target and apply finalization. |
| virtual void finalize(OnFinalizedFunction OnFinalized) = 0; |
| ``` |
| |
| The linking process will call the `finalize` method on the `InFlightAlloc` |
| object if linking succeeds up to the finalization step, otherwise it will call |
| `abandon` to indicate that some error occurred during linking. A call to the |
| `InFlightAlloc::finalize` method should cause content for the allocation to be |
| transferred from working to executor memory, and permissions to be run. A call |
| to `abandon` should result in both kinds of memory being deallocated. |
| |
| On successful finalization, the `InFlightAlloc::finalize` method should |
| construct a `FinalizedAlloc` object (an opaque uint64_t id that the |
| `JITLinkMemoryManager` can use to identify executor memory for deallocation) |
| and pass it to the `OnFinalized` callback. |
| |
| Finalized allocations (represented by `FinalizedAlloc` objects) can be |
| deallocated by calling the `JITLinkMemoryManager::dealloc` method. This method |
| takes a vector of `FinalizedAlloc` objects, since it is common to deallocate |
| multiple objects at the same time and this allows us to batch these requests for |
| transmission to the executing process. |
| |
| JITLink provides a simple in-process implementation of this interface: |
| `InProcessMemoryManager`. It allocates pages once and re-uses them as both |
| working and target memory. |
| |
| ORC provides a cross-process-capable `MapperJITLinkMemoryManager` that can use |
| shared memory or ORC-RPC-based communication to transfer content to the executing |
| process. |
| |
| ### JITLinkMemoryManager and Security |
| |
| JITLink's ability to link JIT'd code for a separate executor process can be |
| used to improve the security of a JIT system: The executor process can be |
| sandboxed, run within a VM, or even run on a fully separate machine. |
| |
| JITLink's memory manager interface is flexible enough to allow for a range of |
| trade-offs between performance and security. For example, on a system where code |
| pages must be signed (preventing code from being updated), the memory manager |
| can deallocate working memory pages after linking to free memory in the process |
| running JITLink. Alternatively, on a system that allows RWX pages, the memory |
| manager may use the same pages for both working and target memory by marking |
| them as RWX, allowing code to be modified in place without further overhead. |
| Finally, if RWX pages are not permitted but dual-virtual-mappings of |
| physical memory pages are, then the memory manager can dual map physical pages |
| as RW- in the JITLink process and R-X in the executor process, allowing |
| modification from the JITLink process but not from the executor (at the cost of |
| extra administrative overhead for the dual mapping). |
| |
| ### Error Handling |
| |
| JITLink makes extensive use of the `llvm::Error` type (see the error handling |
| section of {doc}`ProgrammersManual` for details). The link process itself, all |
| passes, the memory manager interface, and operations on the `JITLinkContext` |
| are all permitted to fail. Link graph construction utilities (especially parsers |
| for object formats) are encouraged to validate input, and validate fixups |
| (e.g. with range checks) before application. |
| |
| Any error will halt the link process and notify the context of failure. In ORC, |
| reported failures are propagated to queries pending on definitions provided by |
| the failing link, and also through edges of the dependence graph to any queries |
| waiting on dependent symbols. |
| |
| (connection_to_orc_runtime)= |
| |
| ## Connection to the ORC Runtime |
| |
| The ORC Runtime (currently under development) aims to provide runtime support |
| for advanced JIT features, including object format features that require |
| non-trivial action in the executor (e.g. running initializers, managing thread |
| local storage, registering with language runtimes, etc.). |
| |
| ORC Runtime support for object format features typically requires cooperation |
| between the runtime (which executes in the executor process) and JITLink (which |
| runs in the JIT process and can inspect LinkGraphs to determine what actions |
| must be taken in the executor). For example: Execution of MachO static |
| initializers in the ORC runtime is performed by the `jit_dlopen` function, |
| which calls back to the JIT process to ask for the list of address ranges of |
| `__mod_init` sections to walk. This list is collated by the |
| `MachOPlatformPlugin`, which installs a pass to record this information for |
| each object as it is linked into the target. |
| |
| (constructing_linkgraphs)= |
| |
| ## Constructing LinkGraphs |
| |
| Clients usually access and manipulate `LinkGraph` instances that were created |
| for them by an `ObjectLinkingLayer` instance, but they can be created manually: |
| |
| 1. By directly constructing and populating a `LinkGraph` instance. |
| 2. By using the `createLinkGraph` family of functions to create a |
| `LinkGraph` from an in-memory buffer containing an object file. This is how |
| `ObjectLinkingLayer` usually creates `LinkGraphs`. |
| |
| 1. `createLinkGraph_<Object-Format>_<Architecture>` can be used when |
| both the object format and architecture are known ahead of time. |
| 2. `createLinkGraph_<Object-Format>` can be used when the object format is |
| known ahead of time, but the architecture is not. In this case the |
| architecture will be determined by inspection of the object header. |
| 3. `createLinkGraph` can be used when neither the object format nor |
| the architecture are known ahead of time. In this case the object header |
| will be inspected to determine both the format and architecture. |
| |
| (jit_linking)= |
| |
| ## JIT Linking |
| |
| The JIT linker concept was introduced in LLVM's earlier generation of JIT APIs, |
| MCJIT. In MCJIT the *RuntimeDyld* component enabled re-use of LLVM as an |
| in-memory compiler by adding an in-memory link step to the end of the usual |
| compiler pipeline. Rather than dumping relocatable objects to disk as a compiler |
| usually would, MCJIT passed them to RuntimeDyld to be linked into a target |
| process. |
| |
| This approach to linking differs from standard *static* or *dynamic* linking: |
| |
| A *static linker* takes one or more relocatable object files as input and links |
| them into an executable or dynamic library on disk. |
| |
| A *dynamic linker* applies relocations to executables and dynamic libraries that |
| have been loaded into memory. |
| |
| A *JIT linker* takes a single relocatable object file at a time and links it |
| into a target process, usually using a context object to allow the linked code |
| to resolve symbols in the target. |
| |
| ### RuntimeDyld |
| |
| In order to keep RuntimeDyld's implementation simple MCJIT imposed some |
| restrictions on compiled code: |
| |
| 1. It had to use the Large code model, and often restricted available relocation |
| models in order to limit the kinds of relocations that had to be supported. |
| 2. It required strong linkage and default visibility on all symbols -- behavior |
| for other linkages/visibilities was not well defined. |
| 3. It constrained and/or prohibited the use of features requiring runtime |
| support, e.g. static initializers or thread local storage. |
| |
| As a result of these restrictions not all language features supported by LLVM |
| worked under MCJIT, and objects to be loaded under the JIT had to be compiled to |
| target it (precluding the use of precompiled code from other sources under the |
| JIT). |
| |
| RuntimeDyld also provided very limited visibility into the linking process |
| itself: Clients could access conservative estimates of section size |
| (RuntimeDyld bundled stub size and padding estimates into the section size |
| value) and the final relocated bytes, but could not access RuntimeDyld's |
| internal object representations. |
| |
| Eliminating these restrictions and limitations was one of the primary motivations |
| for the development of JITLink. |
| |
| ## The llvm-jitlink tool |
| |
| The `llvm-jitlink` tool is a command line wrapper for the JITLink library. |
| It loads some set of relocatable object files and then links them using |
| JITLink. Depending on the options used it will then execute them, or validate |
| the linked memory. |
| |
| The `llvm-jitlink` tool was originally designed to aid JITLink development by |
| providing a simple environment for testing. |
| |
| ### Basic usage |
| |
| By default, `llvm-jitlink` will link the set of objects passed on the command |
| line, then search for a "main" function and execute it: |
| |
| ```sh |
| % cat hello-world.c |
| #include <stdio.h> |
| |
| int main(int argc, char *argv[]) { |
| printf("hello, world!\n"); |
| return 0; |
| } |
| |
| % clang -c -o hello-world.o hello-world.c |
| % llvm-jitlink hello-world.o |
| Hello, World! |
| ``` |
| |
| Multiple objects may be specified, and arguments may be provided to the JIT'd |
| main function using the -args option: |
| |
| ```sh |
| % cat print-args.c |
| #include <stdio.h> |
| |
| void print_args(int argc, char *argv[]) { |
| for (int i = 0; i != argc; ++i) |
| printf("arg %i is \"%s\"\n", i, argv[i]); |
| } |
| |
| % cat print-args-main.c |
| void print_args(int argc, char *argv[]); |
| |
| int main(int argc, char *argv[]) { |
| print_args(argc, argv); |
| return 0; |
| } |
| |
| % clang -c -o print-args.o print-args.c |
| % clang -c -o print-args-main.o print-args-main.c |
| % llvm-jitlink print-args.o print-args-main.o -args a b c |
| arg 0 is "a" |
| arg 1 is "b" |
| arg 2 is "c" |
| ``` |
| |
| Alternative entry points may be specified using the `-entry <entry point |
| name>` option. |
| |
| Other options can be found by calling `llvm-jitlink -help`. |
| |
| ### llvm-jitlink as a regression testing utility |
| |
| One of the primary aims of `llvm-jitlink` was to enable readable regression |
| tests for JITLink. To do this it supports two options: |
| |
| The `-noexec` option tells llvm-jitlink to stop after looking up the entry |
| point, and before attempting to execute it. Since the linked code is not |
| executed, this can be used to link for other targets even if you do not have |
| access to the target being linked (the `-define-abs` or `-phony-externals` |
| options can be used to supply any missing definitions in this case). |
| |
| The `-check <check-file>` option can be used to run a set of `jitlink-check` |
| expressions against working memory. It is typically used in conjunction with |
| `-noexec`, since the aim is to validate JIT'd memory rather than to run the |
| code and `-noexec` allows us to link for any supported target architecture |
| from the current process. In `-check` mode, `llvm-jitlink` will scan the |
| given check-file for lines of the form `# jitlink-check: <expr>`. See |
| examples of this usage in `llvm/test/ExecutionEngine/JITLink`. |
| |
| ### Remote execution via llvm-jitlink-executor |
| |
| By default `llvm-jitlink` will link the given objects into its own process, |
| but this can be overridden by two options: |
| |
| The `-oop-executor[=/path/to/executor]` option tells `llvm-jitlink` to |
| execute the given executor (which defaults to `llvm-jitlink-executor`) and |
| communicate with it via file descriptors which it passes to the executor |
| as the first argument with the format `filedescs=<in-fd>,<out-fd>`. |
| |
| The `-oop-executor-connect=<host>:<port>` option tells `llvm-jitlink` to |
| connect to an already running executor via TCP on the given host and port. To |
| use this option you will need to start `llvm-jitlink-executor` manually with |
| `listen=<host>:<port>` as the first argument. |
| |
| ### Harness mode |
| |
| The `-harness` option allows a set of input objects to be designated as a test |
| harness, with the regular object files implicitly treated as objects to be |
| tested. Definitions of symbols in the harness set override definitions in the |
| test set, and external references from the harness cause automatic scope |
| promotion of local symbols in the test set (these modifications to the usual |
| linker rules are accomplished via an `ObjectLinkingLayer::Plugin` installed by |
| `llvm-jitlink` when it sees the `-harness` option). |
| |
| With these modifications in place we can selectively test functions in an object |
| file by mocking those function's callees. For example, suppose we have an object |
| file, `test_code.o`, compiled from the following C source (which we need not |
| have access to): |
| |
| ```c |
| void irrelevant_function() { irrelevant_external(); } |
| |
| int function_to_mock(int X) { |
| return /* some function of X */; |
| } |
| |
| static void function_to_test() { |
| ... |
| int Y = function_to_mock(); |
| printf("Y is %i\n", Y); |
| } |
| ``` |
| |
| If we want to know how `function_to_test` behaves when we change the behavior |
| of `function_to_mock` we can test it by writing a test harness: |
| |
| ```c |
| void function_to_test(); |
| |
| int function_to_mock(int X) { |
| printf("used mock utility function\n"); |
| return 42; |
| } |
| |
| int main(int argc, char *argv[]) { |
| function_to_test(): |
| return 0; |
| } |
| ``` |
| |
| Under normal circumstances these objects could not be linked together: |
| `function_to_test` is static and could not be resolved outside |
| `test_code.o`, the two `function_to_mock` functions would result in a |
| duplicate definition error, and `irrelevant_external` is undefined. |
| However, using `-harness` and `-phony-externals` we can run this code |
| with: |
| |
| ```sh |
| % clang -c -o test_code_harness.o test_code_harness.c |
| % llvm-jitlink -phony-externals test_code.o -harness test_code_harness.o |
| used mock utility function |
| Y is 42 |
| ``` |
| |
| The `-harness` option may be of interest to people who want to perform some |
| very late testing on build products to verify that compiled code behaves as |
| expected. On basic C test cases this is relatively straightforward. Mocks for |
| more complicated languages (e.g. C++) are much trickier: Any code involving |
| classes tends to have a lot of non-trivial surface area (e.g. vtables) that |
| would require great care to mock. |
| |
| ### Tips for JITLink backend developers |
| |
| 1. Make liberal use of assert and `llvm::Error`. Do *not* assume that the input |
| object is well formed: Return any errors produced by libObject (or your own |
| object parsing code) and validate as you construct. Think carefully about the |
| distinction between contract (which should be validated with asserts and |
| llvm_unreachable) and environmental errors (which should generate |
| `llvm::Error` instances). |
| 2. Don't assume you're linking in-process. Use libSupport's sized, |
| endian-specific types when reading/writing content in the `LinkGraph`. |
| |
| As a "minimum viable" JITLink wrapper, the `llvm-jitlink` tool is an |
| invaluable resource for developers bringing in a new JITLink backend. A standard |
| workflow is to start by throwing an unsupported object at the tool and seeing |
| what error is returned, then fixing that (you can often make a reasonable guess |
| at what should be done based on existing code for other formats or |
| architectures). |
| |
| In debug builds of LLVM, the `-debug-only=jitlink` option dumps logs from the |
| JITLink library during the link process. These can be useful for spotting some bugs at |
| a glance. The `-debug-only=llvm_jitlink` option dumps logs from the `llvm-jitlink` |
| tool, which can be useful for debugging both testcases (it is often less verbose than |
| `-debug-only=jitlink`) and the tool itself. |
| |
| The `-oop-executor` and `-oop-executor-connect` options are helpful for testing |
| handling of cross-process and cross-architecture use cases. |
| |
| ## Roadmap |
| |
| JITLink is under active development. The MachO and ELF backends are mature, with |
| ELF support spanning x86-64, arm64, RISC-V, LoongArch, PowerPC 64, arm32, |
| SystemZ, and Hexagon. A COFF backend supports x86-64 on Windows. Initial XCOFF |
| support for PowerPC 64 is available but not yet usable for general JIT compilation. |
| |
| Major outstanding projects include: |
| |
| - Improve XCOFF support. |
| |
| The XCOFF/ppc64 backend exists but does not yet implement the relocation |
| handling needed for general JIT use. Completing this would enable JITLink on |
| AIX. |
| |
| - Continue improving early-stage backends. |
| |
| Some backends (arm32, Hexagon) support common relocations but are not yet |
| ready for general use. Contributions to extend relocation coverage are welcome. |
| |
| - Implement support for other new architectures and formats. |
| |
| ### JITLink Availability and Feature Status |
| |
| The following table describes the status of the JITlink backends for various |
| format / architecture combinations (as of May 2026). |
| |
| Support levels: |
| |
| - None: No backend. JITLink will return an "architecture not supported" error. |
| Represented by empty cells in the table below. |
| - Skeleton: A backend exists, but does not support commonly used relocations. |
| Even simple programs are likely to trigger an "unsupported relocation" error. |
| Backends in this state may be easy to improve by implementing new relocations. |
| Consider getting involved! |
| - Basic: The backend supports simple programs, isn't ready for general use yet. |
| - Usable: The backend is useable for general use for at least one code and |
| relocation model. |
| - Good: The backend supports almost all relocations. Advanced features like |
| native thread local storage may not be available yet. |
| - Complete: The backend supports all relocations and object format features. |
| |
| ```{list-table} Availability and Status |
| :widths: 10 22 22 22 22 |
| :header-rows: 1 |
| :stub-columns: 1 |
| |
| * - Architecture |
| - ELF |
| - COFF |
| - MachO |
| - XCOFF |
| * - arm32 |
| - Basic |
| - |
| - |
| - |
| * - arm64 |
| - Usable |
| - |
| - Good |
| - |
| * - Hexagon |
| - Basic |
| - |
| - |
| - |
| * - LoongArch |
| - Good |
| - |
| - |
| - |
| * - PowerPC 64 |
| - Usable |
| - |
| - |
| - Skeleton |
| * - RISC-V |
| - Good |
| - |
| - |
| - |
| * - SystemZ |
| - Usable |
| - |
| - |
| - |
| * - x86-32 |
| - Basic |
| - |
| - |
| - |
| * - x86-64 |
| - Good |
| - Usable |
| - Good |
| - |
| ``` |
| |
| [^1]: See `llvm/examples/OrcV2Examples/LLJITWithObjectLinkingLayerPlugin` for |
| a full worked example. |
| |
| [^2]: If not for *hidden* scoped symbols we could eliminate the |
| `JITLinkDylib*` argument to `JITLinkMemoryManager::allocate` and |
| treat every object as a separate simulated dylib for the purposes of |
| memory layout. Hidden symbols break this by generating in-range accesses |
| to external symbols, requiring the access and symbol to be allocated |
| within range of one another. That said, providing a pre-reserved address |
| range pool for each simulated dylib guarantees that the relaxation |
| optimizations will kick in for all intra-dylib references, which is good |
| for performance (at the cost of whatever overhead is introduced by |
| reserving the address-range up-front). |