ORC Design and Implementation

Introduction

This document aims to provide a high-level overview of the design andimplementation of the ORC JIT APIs. Except where otherwise stated, alldiscussion applies to the design of the APIs as of LLVM Version 10 (ORCv2).

Use-cases

ORC provides a modular API for building JIT compilers. There are a rangeof use cases for such an API. For example:

  1. The LLVM tutorials use a simple ORC-based JIT class to execute expressionscompiled from a toy language: Kaleidoscope.

  2. The LLVM debugger, LLDB, uses a cross-compiling JIT for expressionevaluation. In this use case, cross compilation allows expressions compiledin the debugger process to be executed on the debug target process, which maybe on a different device/architecture.

  3. In high-performance JITs (e.g. JVMs, Julia) that want to make use of LLVM’soptimizations within an existing JIT infrastructure.

  • In interpreters and REPLs, e.g. Cling (C++) and the Swift interpreter.By adopting a modular, library-based design we aim to make ORC useful in as manyof these contexts as possible.

Features

ORC provides the following features:

  • JIT-linking
  • ORC provides APIs to link relocatable object files (COFF, ELF, MachO) [1]into a target process at runtime. The target process may be the same processthat contains the JIT session object and jit-linker, or may be another process(even one running on a different machine or architecture) that communicateswith the JIT via RPC.
  • LLVM IR compilation
  • ORC provides off the shelf components (IRCompileLayer, SimpleCompiler,ConcurrentIRCompiler) that make it easy to add LLVM IR to a JIT’d process.
  • Eager and lazy compilation
  • By default, ORC will compile symbols as soon as they are looked up in the JITsession object (ExecutionSession). Compiling eagerly by default makes iteasy to use ORC as a simple in-memory compiler within an existing JITinfrastructure. However ORC also provides support for lazy compilation vialazy-reexports (see Laziness).
  • Support for Custom Compilers and Program Representations
  • Clients can supply custom compilers for each symbol that they define in theirJIT session. ORC will run the user-supplied compiler when the a definition ofa symbol is needed. ORC is actually fully language agnostic: LLVM IR is nottreated specially, and is supported via the same wrapper mechanism (theMaterializationUnit class) that is used for custom compilers.
  • Concurrent JIT’d code and Concurrent Compilation
  • JIT’d code may spawn multiple threads, and may re-enter the JIT (e.g. for lazycompilation) concurrently from multiple threads. The ORC APIs also supportrunning multiple compilers concurrently. Built-in dependency tracking (via theJIT linker) ensures that ORC does not release code for execution until it issafe to call.
  • Orthogonality and Composability
  • Each of the features above can be used (or not) independently. It is possibleto put ORC components together to make a non-lazy, in-process, single threadedJIT or a lazy, out-of-process, concurrent JIT, or anything in between.

LLJIT and LLLazyJIT

ORC provides two basic JIT classes off-the-shelf. These are useful both asexamples of how to assemble ORC components to make a JIT, and as replacementsfor earlier LLVM JIT APIs (e.g. MCJIT).

The LLJIT class uses an IRCompileLayer and RTDyldObjectLinkingLayer to supportcompilation of LLVM IR and linking of relocatable object files. All operationsare performed eagerly on symbol lookup (i.e. a symbol’s definition is compiledas soon as you attempt to look up its address). LLJIT is a suitable replacementfor MCJIT in most cases (note: some more advanced features, e.g.JITEventListeners are not supported yet).

The LLLazyJIT extends LLJIT and adds a CompileOnDemandLayer to enable lazycompilation of LLVM IR. When an LLVM IR module is added via the addLazyIRModulemethod, function bodies in that module will not be compiled until they are firstcalled. LLLazyJIT aims to provide a replacement of LLVM’s original (pre-MCJIT)JIT API.

LLJIT and LLLazyJIT instances can be created using their respective builderclasses: LLJITBuilder and LLazyJITBuilder. For example, assuming you have amodule M loaded on a ThreadSafeContext Ctx:

  1. // Try to detect the host arch and construct an LLJIT instance.
  2. auto JIT = LLJITBuilder().create();
  3.  
  4. // If we could not construct an instance, return an error.
  5. if (!JIT)
  6. return JIT.takeError();
  7.  
  8. // Add the module.
  9. if (auto Err = JIT->addIRModule(TheadSafeModule(std::move(M), Ctx)))
  10. return Err;
  11.  
  12. // Look up the JIT'd code entry point.
  13. auto EntrySym = JIT->lookup("entry");
  14. if (!EntrySym)
  15. return EntrySym.takeError();
  16.  
  17. // Cast the entry point address to a function pointer.
  18. auto *Entry = (void(*)())EntrySym.getAddress();
  19.  
  20. // Call into JIT'd code.
  21. Entry();

The builder classes provide a number of configuration options that can bespecified before the JIT instance is constructed. For example:

  1. // Build an LLLazyJIT instance that uses four worker threads for compilation,
  2. // and jumps to a specific error handler (rather than null) on lazy compile
  3. // failures.
  4.  
  5. void handleLazyCompileFailure() {
  6. // JIT'd code will jump here if lazy compilation fails, giving us an
  7. // opportunity to exit or throw an exception into JIT'd code.
  8. throw JITFailed();
  9. }
  10.  
  11. auto JIT = LLLazyJITBuilder()
  12. .setNumCompileThreads(4)
  13. .setLazyCompileFailureAddr(
  14. toJITTargetAddress(&handleLazyCompileFailure))
  15. .create();
  16.  
  17. // ...

For users wanting to get started with LLJIT a minimal example program can befound at llvm/examples/HowToUseLLJIT.

Design Overview

ORC’s JIT’d program model aims to emulate the linking and symbol resolutionrules used by the static and dynamic linkers. This allows ORC to JITarbitrary LLVM IR, including IR produced by an ordinary static compiler (e.g.clang) that uses constructs like symbol linkage and visibility, and weak [3]and common symbol definitions.

To see how this works, imagine a program foo which links against a pairof dynamic libraries: libA and libB. On the command line, building thisprogram might look like:

  1. $ clang++ -shared -o libA.dylib a1.cpp a2.cpp
  2. $ clang++ -shared -o libB.dylib b1.cpp b2.cpp
  3. $ clang++ -o myapp myapp.cpp -L. -lA -lB
  4. $ ./myapp

In ORC, this would translate into API calls on a “CXXCompilingLayer” (with errorchecking omitted for brevity) as:

  1. ExecutionSession ES;
  2. RTDyldObjectLinkingLayer ObjLinkingLayer(
  3. ES, []() { return std::make_unique<SectionMemoryManager>(); });
  4. CXXCompileLayer CXXLayer(ES, ObjLinkingLayer);
  5.  
  6. // Create JITDylib "A" and add code to it using the CXX layer.
  7. auto &LibA = ES.createJITDylib("A");
  8. CXXLayer.add(LibA, MemoryBuffer::getFile("a1.cpp"));
  9. CXXLayer.add(LibA, MemoryBuffer::getFile("a2.cpp"));
  10.  
  11. // Create JITDylib "B" and add code to it using the CXX layer.
  12. auto &LibB = ES.createJITDylib("B");
  13. CXXLayer.add(LibB, MemoryBuffer::getFile("b1.cpp"));
  14. CXXLayer.add(LibB, MemoryBuffer::getFile("b2.cpp"));
  15.  
  16. // Specify the search order for the main JITDylib. This is equivalent to a
  17. // "links against" relationship in a command-line link.
  18. ES.getMainJITDylib().setSearchOrder({{&LibA, false}, {&LibB, false}});
  19. CXXLayer.add(ES.getMainJITDylib(), MemoryBuffer::getFile("main.cpp"));
  20.  
  21. // Look up the JIT'd main, cast it to a function pointer, then call it.
  22. auto MainSym = ExitOnErr(ES.lookup({&ES.getMainJITDylib()}, "main"));
  23. auto *Main = (int(*)(int, char*[]))MainSym.getAddress();
  24.  
  25. int Result = Main(...);

This example tells us nothing about how or when compilation will happen.That will depend on the implementation of the hypothetical CXXCompilingLayer.The same linker-based symbol resolution rules will apply regardless of thatimplementation, however. For example, if a1.cpp and a2.cpp both define afunction “foo” then ORCv2 will generate a duplicate definition error. On theother hand, if a1.cpp and b1.cpp both define “foo” there is no error (differentdynamic libraries may define the same symbol). If main.cpp refers to “foo”, itshould bind to the definition in LibA rather than the one in LibB, sincemain.cpp is part of the “main” dylib, and the main dylib links against LibAbefore LibB.

Many JIT clients will have no need for this strict adherence to the usualahead-of-time linking rules, and should be able to get by just fine by puttingall of their code in a single JITDylib. However, clients who want to JIT codefor languages/projects that traditionally rely on ahead-of-time linking (e.g.C++) will find that this feature makes life much easier.

Symbol lookup in ORC serves two other important functions, beyond providingaddresses for symbols: (1) It triggers compilation of the symbol(s) searched for(if they have not been compiled already), and (2) it provides thesynchronization mechanism for concurrent compilation. The pseudo-code for thelookup process is:

  1. construct a query object from a query set and query handler
  2. lock the session
  3. lodge query against requested symbols, collect required materializers (if any)
  4. unlock the session
  5. dispatch materializers (if any)

In this context a materializer is something that provides a working definitionof a symbol upon request. Usually materializers are just wrappers for compilers,but they may also wrap a jit-linker directly (if the program representationbacking the definitions is an object file), or may even be a class that writesbits directly into memory (for example, if the definitions arestubs). Materialization is the blanket term for any actions (compiling, linking,splatting bits, registering with runtimes, etc.) that are required to generate asymbol definition that is safe to call or access.

As each materializer completes its work it notifies the JITDylib, which in turnnotifies any query objects that are waiting on the newly materializeddefinitions. Each query object maintains a count of the number of symbols thatit is still waiting on, and once this count reaches zero the query object callsthe query handler with a SymbolMap (a map of symbol names to addresses)describing the result. If any symbol fails to materialize the query immediatelycalls the query handler with an error.

The collected materialization units are sent to the ExecutionSession to bedispatched, and the dispatch behavior can be set by the client. By default eachmaterializer is run on the calling thread. Clients are free to create newthreads to run materializers, or to send the work to a work queue for a threadpool (this is what LLJIT/LLLazyJIT do).

Top Level APIs

Many of ORC’s top-level APIs are visible in the example above:

  • ExecutionSession represents the JIT’d program and provides context for theJIT: It contains the JITDylibs, error reporting mechanisms, and dispatches thematerializers.
  • JITDylibs provide the symbol tables.
  • Layers (ObjLinkingLayer and CXXLayer) are wrappers around compilers andallow clients to add uncompiled program representations supported by thosecompilers to JITDylibs.

Several other important APIs are used explicitly. JIT clients need not be awareof them, but Layer authors will use them:

  • MaterializationUnit - When XXXLayer::add is invoked it wraps the givenprogram representation (in this example, C++ source) in a MaterializationUnit,which is then stored in the JITDylib. MaterializationUnits are responsible fordescribing the definitions they provide, and for unwrapping the programrepresentation and passing it back to the layer when compilation is required(this ownership shuffle makes writing thread-safe layers easier, since theownership of the program representation will be passed back on the stack,rather than having to be fished out of a Layer member, which would requiresynchronization).
  • MaterializationResponsibility - When a MaterializationUnit hands a programrepresentation back to the layer it comes with an associatedMaterializationResponsibility object. This object tracks the definitionsthat must be materialized and provides a way to notify the JITDylib once theyare either successfully materialized or a failure occurs.

Absolute Symbols, Aliases, and Reexports

ORC makes it easy to define symbols with absolute addresses, or symbols thatare simply aliases of other symbols:

Absolute Symbols

Absolute symbols are symbols that map directly to addresses without requiringfurther materialization, for example: “foo” = 0x1234. One use case forabsolute symbols is allowing resolution of process symbols. E.g.

With this mapping established code added to the JIT can refer to printfsymbolically rather than requiring the address of printf to be “baked in”.This in turn allows cached versions of the JIT’d code (e.g. compiled objects)to be re-used across JIT sessions as the JIT’d code no longer changes, only theabsolute symbol definition does.

For process and library symbols the DynamicLibrarySearchGenerator utility (SeeHow to Add Process and Library Symbols to JITDylibs) can be used to automatically build absolutesymbol mappings for you. However the absoluteSymbols function is still usefulfor making non-global objects in your JIT visible to JIT’d code. For example,imagine that your JIT standard library needs access to your JIT object to makesome calls. We could bake the address of your object into the library, but thenit would need to be recompiled for each session:

We can turn this into a symbolic reference in the JIT standard library:

And then make our JIT object visible to the JIT standard library with anabsolute symbol definition when the JIT is started:

Aliases and Reexports

Aliases and reexports allow you to define new symbols that map to existingsymbols. This can be useful for changing linkage relationships between symbolsacross sessions without having to recompile code. For example, imagine thatJIT’d code has access to a log function, void log(const char*) for whichthere are two implementations in the JIT standard library: log_fast andlog_detailed. Your JIT can choose which one of these definitions will beused when the log symbol is referenced by setting up an alias at JIT startuptime:

The symbolAliases function allows you to define aliases within a singleJITDylib. The reexports function provides the same functionality, butoperates across JITDylib boundaries. E.g.

The reexports utility can be handy for composing a single JITDylib interface byre-exporting symbols from several other JITDylibs.

Laziness

Laziness in ORC is provided by a utility called “lazy reexports”. A lazyreexport is similar to a regular reexport or alias: It provides a new name foran existing symbol. Unlike regular reexports however, lookups of lazy reexportsdo not trigger immediate materialization of the reexported symbol. Instead, theyonly trigger materialization of a function stub. This function stub isinitialized to point at a lazy call-through, which provides reentry into theJIT. If the stub is called at runtime then the lazy call-through will look upthe reexported symbol (triggering materialization for it if necessary), updatethe stub (to call directly to the reexported symbol on subsequent calls), andthen return via the reexported symbol. By re-using the existing symbol lookupmechanism, lazy reexports inherit the same concurrency guarantees: calls to lazyreexports can be made from multiple threads concurrently, and the reexportedsymbol can be any state of compilation (uncompiled, already in the process ofbeing compiled, or already compiled) and the call will succeed. This allowslaziness to be safely mixed with features like remote compilation, concurrentcompilation, concurrent JIT’d code, and speculative compilation.

There is one other key difference between regular reexports and lazy reexportsthat some clients must be aware of: The address of a lazy reexport will bedifferent from the address of the reexported symbol (whereas a regularreexport is guaranteed to have the same address as the reexported symbol).Clients who care about pointer equality will generally want to use the addressof the reexport as the canonical address of the reexported symbol. This willallow the address to be taken without forcing materialization of the reexport.

Usage example:

If JITDylib JD contains definitions for symbols foo_body andbar_body, we can create lazy entry points Foo and Bar in JITDylibJD2 by calling:

  1. auto ReexportFlags = JITSymbolFlags::Exported | JITSymbolFlags::Callable;
  2. JD2.define(
  3. lazyReexports(CallThroughMgr, StubsMgr, JD,
  4. SymbolAliasMap({
  5. { Mangle("foo"), { Mangle("foo_body"), ReexportedFlags } },
  6. { Mangle("bar"), { Mangle("bar_body"), ReexportedFlags } }
  7. }));

A full example of how to use lazyReexports with the LLJIT class can be found atllvm_project/llvm/examples/LLJITExamples/LLJITWithLazyReexports.

Supporting Custom Compilers

TBD.

Transitioning from ORCv1 to ORCv2

Since LLVM 7.0, new ORC development work has focused on adding support forconcurrent JIT compilation. The new APIs (including new layer interfaces andimplementations, and new utilities) that support concurrency are collectivelyreferred to as ORCv2, and the original, non-concurrent layers and utilitiesare now referred to as ORCv1.

The majority of the ORCv1 layers and utilities were renamed with a ‘Legacy’prefix in LLVM 8.0, and have deprecation warnings attached in LLVM 9.0. In LLVM10.0 ORCv1 will be removed entirely.

Transitioning from ORCv1 to ORCv2 should be easy for most clients. Most of theORCv1 layers and utilities have ORCv2 counterparts [2] that can be directlysubstituted. However there are some design differences between ORCv1 and ORCv2to be aware of:

  1. ORCv2 fully adopts the JIT-as-linker model that began with MCJIT. Modules(and other program representations, e.g. Object Files) are no longer addeddirectly to JIT classes or layers. Instead, they are added to JITDylibinstances by layers. The JITDylib determines where the definitionsreside, the layers determine how the definitions will be compiled.Linkage relationships between JITDylibs determine how inter-modulereferences are resolved, and symbol resolvers are no longer used. See thesection Design Overview for more details.

    Unless multiple JITDylibs are needed to model linkage relationships, ORCv1clients should place all code in the main JITDylib (returned byExecutionSession::getMainJITDylib()). MCJIT clients should use LLJIT(see LLJIT and LLLazyJIT).

  2. All JIT stacks now need an ExecutionSession instance. ExecutionSessionmanages the string pool, error reporting, synchronization, and symbollookup.

  3. ORCv2 uses uniqued strings (SymbolStringPtr instances) rather thanstring values in order to reduce memory overhead and improve lookupperformance. See the subsection How to manage symbol strings.

  4. IR layers require ThreadSafeModule instances, rather thanstd::unique_ptr<Module>s. ThreadSafeModule is a wrapper that ensures thatModules that use the same LLVMContext are not accessed concurrently.See How to use ThreadSafeModule and ThreadSafeContext.

  5. Symbol lookup is no longer handled by layers. Instead, there is alookup method on JITDylib that takes a list of JITDylibs to scan.

    1. ExecutionSession ES;JITDylib &JD1 = …;JITDylib &JD2 = …;auto Sym = ES.lookup({&JD1, &JD2}, ES.intern("_main"));
  6. Module removal is not yet supported. There is no equivalent of thelayer concept removeModule/removeObject methods. Work on resource trackingand removal in ORCv2 is ongoing.

For code examples and suggestions of how to use the ORCv2 APIs, please seethe section How-tos.

How-tos

How to manage symbol strings

Symbol strings in ORC are uniqued to improve lookup performance, reduce memoryoverhead, and allow symbol names to function as efficient keys. To get theunique SymbolStringPtr for a string value, call theExecutionSession::intern method:

  1. ExecutionSession ES;/// …auto MainSymbolName = ES.intern("main");

If you wish to perform lookup using the C/IR name of a symbol you will alsoneed to apply the platform linker-mangling before interning the string. OnLinux this mangling is a no-op, but on other platforms it usually involvesadding a prefix to the string (e.g. ‘_’ on Darwin). The mangling scheme isbased on the DataLayout for the target. Given a DataLayout and anExecutionSession, you can create a MangleAndInterner function object thatwill perform both jobs for you:

  1. ExecutionSession ES;const DataLayout &DL = …;MangleAndInterner Mangle(ES, DL);// …// Portable IR-symbol-name lookup:auto Sym = ES.lookup({&ES.getMainJITDylib()}, Mangle("main"));

How to create JITDylibs and set up linkage relationships

In ORC, all symbol definitions reside in JITDylibs. JITDylibs are created bycalling the ExecutionSession::createJITDylib method with a unique name:

  1. ExecutionSession ES;auto &JD = ES.createJITDylib("libFoo.dylib");

The JITDylib is owned by the ExecutionEngine instance and will be freedwhen it is destroyed.

A JITDylib representing the JIT main program is created by ExecutionEngine bydefault. A reference to it can be obtained by callingExecutionSession::getMainJITDylib():

  1. ExecutionSession ES;auto &MainJD = ES.getMainJITDylib();

How to use ThreadSafeModule and ThreadSafeContext

ThreadSafeModule and ThreadSafeContext are wrappers around Modules andLLVMContexts respectively. A ThreadSafeModule is a pair of astd::unique_ptr<Module> and a (possibly shared) ThreadSafeContext value. AThreadSafeContext is a pair of a std::unique_ptr<LLVMContext> and a lock.This design serves two purposes: providing a locking scheme and lifetimemanagement for LLVMContexts. The ThreadSafeContext may be locked to preventaccidental concurrent access by two Modules that use the same LLVMContext.The underlying LLVMContext is freed once all ThreadSafeContext values pointingto it are destroyed, allowing the context memory to be reclaimed as soon asthe Modules referring to it are destroyed.

ThreadSafeContexts can be explicitly constructed from astd::unique_ptr<LLVMContext>:

  1. ThreadSafeContext TSCtx(std::make_unique<LLVMContext>());

ThreadSafeModules can be constructed from a pair of a std::unique_ptr<Module>and a ThreadSafeContext value. ThreadSafeContext values may be shared betweenmultiple ThreadSafeModules:

  1. ThreadSafeModule TSM1( std::make_unique<Module>("M1", TSCtx.getContext()), TSCtx);ThreadSafeModule TSM2( std::make_unique<Module>("M2", TSCtx.getContext()), TSCtx);

Before using a ThreadSafeContext, clients should ensure that either the contextis only accessible on the current thread, or that the context is locked. In theexample above (where the context is never locked) we rely on the fact that bothTSM1 and TSM2, and TSCtx are all created on one thread. If a context isgoing to be shared between threads then it must be locked before any accessingor creating any Modules attached to it. E.g.

  1. ThreadSafeContext TSCtx(std::make_unique<LLVMContext>());ThreadPool TP(NumThreads);JITStack J;for (auto &ModulePath : ModulePaths) { TP.async( & { auto Lock = TSCtx.getLock(); auto M = loadModuleOnContext(ModulePath, TSCtx.getContext()); J.addModule(ThreadSafeModule(std::move(M), TSCtx)); });}TP.wait();

To make exclusive access to Modules easier to manage the ThreadSafeModule classprovides a convenience function, withModuleDo, that implicitly (1) locks theassociated context, (2) runs a given function object, (3) unlocks the context,and (3) returns the result generated by the function object. E.g.

  1. ThreadSafeModule TSM = getModule(…);// Dump the module:size_t NumFunctionsInModule = TSM.withModuleDo( { // <- Context locked before entering lambda. return M.size(); } // <- Context unlocked after leaving. );

Clients wishing to maximize possibilities for concurrent compilation will wantto create every new ThreadSafeModule on a new ThreadSafeContext. For thisreason a convenience constructor for ThreadSafeModule is provided that implicitlyconstructs a new ThreadSafeContext value from a std::unique_ptr<LLVMContext>:

  1. // Maximize concurrency opportunities by loading every module on a// separate context.for (const auto &IRPath : IRPaths) { auto Ctx = std::make_unique<LLVMContext>(); auto M = std::make_unique<LLVMContext>("M", *Ctx); CompileLayer.add(ES.getMainJITDylib(), ThreadSafeModule(std::move(M), std::move(Ctx)));}

Clients who plan to run single-threaded may choose to save memory by loadingall modules on the same context:

  1. // Save memory by using one context for all Modules:ThreadSafeContext TSCtx(std::make_unique<LLVMContext>());for (const auto &IRPath : IRPaths) { ThreadSafeModule TSM(parsePath(IRPath, *TSCtx.getContext()), TSCtx); CompileLayer.add(ES.getMainJITDylib(), ThreadSafeModule(std::move(TSM));}

How to Add Process and Library Symbols to the JITDylibs

JIT’d code typically needs access to symbols in the host program or insupporting libraries. References to process symbols can be “baked in” to codeas it is compiled by turning external references into pre-resolved integerconstants, however this ties the JIT’d code to the current process’s virtualmemory layout (meaning that it can not be cached between runs) and makesdebugging lower level program representations difficult (as all externalreferences are opaque integer values). A bettor solution is to maintain symbolicexternal references and let the jit-linker bind them for you at runtime. Toallow the JIT linker to find these external definitions their addresses mustbe added to a JITDylib that the JIT’d definitions link against.

Adding definitions for external symbols could be done using the absoluteSymbolsfunction:

  1. const DataLayout &DL = getDataLayout();MangleAndInterner Mangle(ES, DL);auto &JD = ES.getMainJITDylib();JD.define( absoluteSymbols({ { Mangle("puts"), pointerToJITTargetAddress(&puts)}, { Mangle("gets"), pointerToJITTargetAddress(&getS)} }));

Manually adding absolute symbols for a large or changing interface is cumbersomehowever, so ORC provides an alternative to generate new definitions on demand:definition generators. If a definition generator is attached to a JITDylib,then any unsuccessful lookup on that JITDylib will fall back to calling thedefinition generator, and the definition generator may choose to generate a newdefinition for the missing symbols. Of particular use here is theDynamicLibrarySearchGenerator utility. This can be used to reflect the wholeexported symbol set of the process or a specific dynamic library, or a subsetof either of these determined by a predicate.

For example, to load the whole interface of a runtime library:

  1. const DataLayout &DL = getDataLayout();auto &JD = ES.getMainJITDylib();JD.setGenerator(DynamicLibrarySearchGenerator::Load("/path/to/lib" DL.getGlobalPrefix()));// IR added to JD can now link against all symbols exported by the library// at '/path/to/lib'.CompileLayer.add(JD, loadModule(…));

Or, to expose a whitelisted set of symbols from the main process:

  1. const DataLayout &DL = getDataLayout();MangleAndInterner Mangle(ES, DL);auto &JD = ES.getMainJITDylib();DenseSet<SymbolStringPtr> Whitelist({ Mangle("puts"), Mangle("gets") });// Use GetForCurrentProcess with a predicate function that checks the// whitelist.JD.setGenerator( DynamicLibrarySearchGenerator::GetForCurrentProcess( DL.getGlobalPrefix(), & { return Whitelist.count(S); }));// IR added to JD can now link against any symbols exported by the process// and contained in the whitelist.CompileLayer.add(JD, loadModule(…));

Future Features

TBD: Speculative compilation. Object Caches.

[1]Formats/architectures vary in terms of supported features. MachO andELF tend to have better support than COFF. Patches very welcome!
[2]The LazyEmittingLayer, RemoteObjectClientLayer andRemoteObjectServerLayer do not have counterparts in the newsystem. In the case of LazyEmittingLayer it was simply no longerneeded: in ORCv2, deferring compilation until symbols are looked up isthe default. The removal of RemoteObjectClientLayer andRemoteObjectServerLayer means that JIT stacks can no longer be splitacross processes, however this functionality appears not to have beenused.
[3]Weak definitions are currently handled correctly within dylibs, but ifmultiple dylibs provide a weak definition of a symbol then each will endup with its own definition (similar to how weak definitions are handledin Windows DLLs). This will be fixed in the future.