MCJIT Design and Implementation
Introduction
This document describes the internal workings of the MCJIT executionengine and the RuntimeDyld component. It is intended as a high leveloverview of the implementation, showing the flow and interactions ofobjects throughout the code generation and dynamic loading process.
Engine Creation
In most cases, an EngineBuilder object is used to create an instance ofthe MCJIT execution engine. The EngineBuilder takes an llvm::Moduleobject as an argument to its constructor. The client may then set variousoptions that we control the later be passed along to the MCJIT engine,including the selection of MCJIT as the engine type to be created.Of particular interest is the EngineBuilder::setMCJITMemoryManagerfunction. If the client does not explicitly create a memory manager atthis time, a default memory manager (specifically SectionMemoryManager)will be created when the MCJIT engine is instantiated.
Once the options have been set, a client calls EngineBuilder::create tocreate an instance of the MCJIT engine. If the client does not use theform of this function that takes a TargetMachine as a parameter, a newTargetMachine will be created based on the target triple associated withthe Module that was used to create the EngineBuilder.EngineBuilder::create will call the static MCJIT::createJIT function,passing in its pointers to the module, memory manager and target machineobjects, all of which will subsequently be owned by the MCJIT object.
The MCJIT class has a member variable, Dyld, which contains an instance ofthe RuntimeDyld wrapper class. This member will be used forcommunications between MCJIT and the actual RuntimeDyldImpl object thatgets created when an object is loaded.Upon creation, MCJIT holds a pointer to the Module object that it receivedfrom EngineBuilder but it does not immediately generate code for thismodule. Code generation is deferred until either theMCJIT::finalizeObject method is called explicitly or a function such asMCJIT::getPointerToFunction is called which requires the code to have beengenerated.
Code Generation
When code generation is triggered, as described above, MCJIT will firstattempt to retrieve an object image from its ObjectCache member, if onehas been set. If a cached object image cannot be retrieved, MCJIT willcall its emitObject method. MCJIT::emitObject uses a local PassManagerinstance and creates a new ObjectBufferStream instance, both of which itpasses to TargetMachine::addPassesToEmitMC before calling PassManager::runon the Module with which it was created.The PassManager::run call causes the MC code generation mechanisms to emita complete relocatable binary object image (either in either ELF or MachOformat, depending on the target) into the ObjectBufferStream object, whichis flushed to complete the process. If an ObjectCache is being used, theimage will be passed to the ObjectCache here.
At this point, the ObjectBufferStream contains the raw object image.Before the code can be executed, the code and data sections from thisimage must be loaded into suitable memory, relocations must be applied andmemory permission and code cache invalidation (if required) must be completed.
Object Loading
Once an object image has been obtained, either through code generation orhaving been retrieved from an ObjectCache, it is passed to RuntimeDyld tobe loaded. The RuntimeDyld wrapper class examines the object to determineits file format and creates an instance of either RuntimeDyldELF orRuntimeDyldMachO (both of which derive from the RuntimeDyldImpl baseclass) and calls the RuntimeDyldImpl::loadObject method to perform thatactual loading.RuntimeDyldImpl::loadObject begins by creating an ObjectImage instancefrom the ObjectBuffer it received. ObjectImage, which wraps theObjectFile class, is a helper class which parses the binary object imageand provides access to the information contained in the format-specificheaders, including section, symbol and relocation information.
RuntimeDyldImpl::loadObject then iterates through the symbols in theimage. Information about common symbols is collected for later use. Foreach function or data symbol, the associated section is loaded into memoryand the symbol is stored in a symbol table map data structure. When theiteration is complete, a section is emitted for the common symbols.
Next, RuntimeDyldImpl::loadObject iterates through the sections in theobject image and for each section iterates through the relocations forthat sections. For each relocation, it calls the format-specificprocessRelocationRef method, which will examine the relocation and storeit in one of two data structures, a section-based relocation list map andan external symbol relocation map.When RuntimeDyldImpl::loadObject returns, all of the code and datasections for the object will have been loaded into memory allocated by thememory manager and relocation information will have been prepared, but therelocations have not yet been applied and the generated code is still notready to be executed.
[Currently (as of August 2013) the MCJIT engine will immediately applyrelocations when loadObject completes. However, this shouldn’t behappening. Because the code may have been generated for a remote target,the client should be given a chance to re-map the section addresses beforerelocations are applied. It is possible to apply relocations multipletimes, but in the case where addresses are to be re-mapped, this firstapplication is wasted effort.]
Address Remapping
At any time after initial code has been generated and beforefinalizeObject is called, the client can remap the address of sections inthe object. Typically this is done because the code was generated for anexternal process and is being mapped into that process’ address space.The client remaps the section address by calling MCJIT::mapSectionAddress.This should happen before the section memory is copied to its newlocation.
When MCJIT::mapSectionAddress is called, MCJIT passes the call on toRuntimeDyldImpl (via its Dyld member). RuntimeDyldImpl stores the newaddress in an internal data structure but does not update the code at thistime, since other sections are likely to change.
When the client is finished remapping section addresses, it will callMCJIT::finalizeObject to complete the remapping process.
Final Preparations
When MCJIT::finalizeObject is called, MCJIT callsRuntimeDyld::resolveRelocations. This function will attempt to locate anyexternal symbols and then apply all relocations for the object.
External symbols are resolved by calling the memory manager’sgetPointerToNamedFunction method. The memory manager will return theaddress of the requested symbol in the target address space. (Note, thismay not be a valid pointer in the host process.) RuntimeDyld will theniterate through the list of relocations it has stored which are associatedwith this symbol and invoke the resolveRelocation method which, through anformat-specific implementation, will apply the relocation to the loadedsection memory.
Next, RuntimeDyld::resolveRelocations iterates through the list ofsections and for each section iterates through a list of relocations thathave been saved which reference that symbol and call resolveRelocation foreach entry in this list. The relocation list here is a list ofrelocations for which the symbol associated with the relocation is locatedin the section associated with the list. Each of these locations willhave a target location at which the relocation will be applied that islikely located in a different section.Once relocations have been applied as described above, MCJIT callsRuntimeDyld::getEHFrameSection, and if a non-zero result is returnedpasses the section data to the memory manager’s registerEHFrames method.This allows the memory manager to call any desired target-specificfunctions, such as registering the EH frame information with a debugger.
Finally, MCJIT calls the memory manager’s finalizeMemory method. In thismethod, the memory manager will invalidate the target code cache, ifnecessary, and apply final permissions to the memory pages it hasallocated for code and data memory.