In the first two articles of this series, I introduced the JitBuilder library and described how to use it to compile native x86-64 code on the Linux platform using a Docker image I built with theEclipse OMR project. In this article, I show you how to build your own JitBuilder library from the source code in the Eclipse OMR project, and I will switch platforms to OS X. Along the way, I’ll give you a tour of all the various parts of the JitBuilder library and where they reside in the Eclipse OMR project.
to understand the basics of how the library works and to download the JitBuilder docker image and get it up and running.
And read thesecond post to build more complicated methods using the JitBuilder library.
Let’s get started! The first step is to clone the Eclipse OMR repository and get the code onto your system:
$ git clone https://github.com/eclipse/omr Cloning into 'omr'... remote: Counting objects: 10694, done. remote: Compressing objects: 100% (2/2), done. remote: Total 10694 (delta 0), reused 0 (delta 0), pack-reused 10692 Receiving objects: 100% (10694/10694), 11.21 MiB | 1.54 MiB/s, done. Resolving deltas: 100% (7054/7054), done.
At this point, you’ve got an
directory containing the complete Eclipse OMR source code repository. Let’s take a quick look at what’s in there:
$ cd omr && ls CONTRIBUTING.md config.guess gc omr_glue_static_lib third_party GNUmakefile config.sub glue omr_static_lib thread INSTALL configure include_core omrmakefiles tools LICENSE configure.ac install-sh omrsigcompat util README.md ddr_artifacts.mk jitbuilder omrtrace artwork doc lib perftest build example nls port compiler fvtest omr run_configure.mk
That’s a lot of stuff! The Eclipse OMR project currently weighs in at a little over 800,000 lines of code, but you don’t need to pay attention to most of it. I’ll just touch upon a few of the highlights.
There are a number of files and directories that play a part in the OMR project’s configuration (i.e. detecting the platform and system capabilities) and build system. OMR currently uses autotools
to generate static makefiles to build.
are all part of the OMR configuration and build story. If you’re interested, there are some more details in the omr/README.md
file, but for this article and for most users of JitBuilder you won’t need to delve into these details. If you want to jump directly to configuring the OMR project, skip ahead a few paragraphs to “Configuring the OMR project“.
The top-level omr directory contains the usual README.md
, and CONTRIBUTING.md
files to give you some background and practical information about the project if you want to get involved (and you’re very welcome to!). There are lots of different components, mostly obviously named like
is the garbage collection framework, in the top level directory, but for this article I’ll focus on only two directories:
directory contains the OMR compiler component code that will be built into the JitBuilder library. It represents a little bit more than half of the compiler that powers the IBM J9 JIT compiler for Java. The
directory contains an extension
of the OMR compiler that augments a few of the OMR compiler classes and implements a simple default lifecycle infrastructure around the use of the OMR compiler, as described in earlier articles in this series. Its internal directory structure purposefully mimics that of the compiler directory so that it can extend the OMR compiler component. That’s all the detail I’ll go into about that in this post. Let’s get that library built!
To build and then use the JitBuilder library, you first need to run the OMR top-level configuration step. Here’s the command you need to run:
$ make -f run_configure.mk SPEC=osx OMRGLUE=./example/glue [ lots of output omitted ]
is a makefile that handles most of the complexity of system configuration for you. It only requires two pieces of input:
what platform to configure for, and
where to find the glue
code that should be compiled with OMR. What is glue code? The OMR project contains what are essentially incomplete technology components. To complete each OMR component you decide to use, you need to write glue code
to connect the component into your language runtime and to configure it to respect your language semantics and runtime mechanisms.
In fact, JitBuilder doesn’t really
is like a glue directory itself), but
expects it so you cannot leave it out. The OMR project comes with an example glue implementation in the
directory, so the easiest thing to do is simply point OMRGLUE at that directory as we did in the above command.
is the platform you want to configure for. Since I’m writing this article for OS X, the option I wrote is
. If you want to run on a 64-bit x86 Linux variant, you could instead set
. With either platform
setting, the rest of this article should work smoothly for you. Please let me know if it doesn’t!
Once you’ve configured OMR, you are ready to build the JitBuilder library.
The code specific to the JitBuilder library can be found in the
top-level directory. Let’s take a look at what’s in there:
$ cd jitbuilder && ls Makefile codegen control ilgen p runtime z build compile env optimizer release x
Because the JitBuilder library is actually a compiler project itself that extends the OMR compiler, most of these directories simply mimic the directory layout of the OMR compiler. Because of the way the OMR compiler is constructed, you can create new compiler projects that extend, or subclass, the functionality in OMR to implement a compiler for a specific language (like Java) or for a particular use case (like creating the JitBuilder library). I won’t talk much more about that here. For those who are really interested, there is some documentation in
The main pieces you need to pay attention to in this article are the
directory. But first, let’s build the JitBuilder library:
$ make OR $ make -jN # if you want to build on N cores [ lots of output omitted ]
That will take a while since it builds all the main source files in the
directories as well as in the OMR compiler directories, but once it’s done, you’ll have successfully built the JitBuilder library!
To verify that the library has been built, let’s descend into the
$ cd release; ls Dockerfile-JitBuilder-x86_64 README.md jitbuilder.tgz LICENSE ReplayMethod.cpp libjitbuilder.a Makefile include src
This directory contains a standalone JitBuilder library distribution,
, which contains everything needed to work against a particular build of the JitBuilder library. Because we configured and built on OS X, you could move
to any OS X system, expand it, and then use the library to dynamically compile native code using the JitBuilder API on that system. It has a very basic
, a copy of the same
file as the Eclipse OMR project (a dual license: Eclipse Public License v1.0 and Apache License 2.0), a
, a Docker file for building a docker image for x86_64 (the same one that’s used in the first two articles of this series), the
(the JitBuilder static library itself), an
directory defining the JitBuilder library API, and a
directory that contains a number of code samples for how to use different parts of the JitBuilder API. There is also
which is part of an experimental and incomplete feature of the JitBuilder library, so you can ignore that for now.
As the earlier articles in this series introduced to you, JitBuilder is a library consisting of a number of useful classes whose API can be primarily found in the
directory. If you’re interested to poke around, the implementation for these classes can be found in the
directory. In fact, as part of building the
directory, the header files are copied from
Here’s a high-level description of the various parts:
contains the high level interface to the JitBuilder library, which currently describes only the three calls introduced in the first article in this series:
is an object that represents the ABI (parameters, return value) and the operations that should be performed when a compiled method is called. To compile your own methods, you subclass
and implement its constructor to specify the ABI and then specify the operations the compiled method should perform by implementing a
member function called
(usually using either
, see below). After passing a
, you get back a pointer to a function that you can call by casting that pointer to a C function prototype. Each
has a hash map of names (C strings) representing the method’s local variables, and you can define them all at once or make them up as you go.
is an object that you can use to describe the shape of structures, unions, and other types so that JitBuilder knows how to access their fields and values.
provides some useful primitives for constructing pointers and for converting C types (like unsigned long) into JitBuilder types (like
) according to how your C/C++ compiler represents those types. In other words, if you build the JitBuilder library with the same C++ compiler used to compile your program, it is very easy to make sure JitBuilder uses type definitions that are consistent with the rest of your code base.
is an object that represents the operations to perform when a particular control flow path is reached. The method entry point is one particular control flow path, and so a
object is also an
object. But you can create arbitrary
objects and connect them together (even nest them) in arbitrary ways using an ever-growing list of services like
. Each service you call on an
appends operations to the control flow path and so describes the intended order of operations to the compiler. There is no restriction on how you add operations to different
objects. You can create all the
objects up front and then add operations to them individually, or you can start from the
object (function entry) and create
objects as you need them to represent the different paths of execution as you require them.
is the superclass of
, providing an even more precise and flexible interface to create OMR compiler IL, but it also requires intimate knowledge of the OMR compiler IL.
is considered a highly advanced topic for JitBuilder based JITs and, frankly, this API has not been fleshed out as much as
. The code samples provided with JitBuilder (see below) all use
and do not reference
is an object that represents the values that are created and consumed by expressions. If you load a local variable by name, the value that’s loaded is an
. If you create a constant integer, that’s an
. If you pass both of those IlValues into the
operation, you’ll get back another
that represents their sum. There is an ever growing list of operations you can apply to create arbitrarily complex IlValue expressions.
is a special kind of
that’s designed to simplify writing JIT compilers for bytecode based languages. You allocate a
object for each bytecode in a method and translate the operations needed for that bytecode using that object. You have to specify every flow edge (even fall through edges) between bytecode builders.
also taps into a handy and mostly automatic built-in worklist algorithm provided by
that can traverse all flow edges in a control flow graph, visiting each bytecode at most once. Any bytecodes in the method that aren’t reachable (many parsers generate unreachable code to simplify the process of initial code generation from a parse tree) will not be visited.
is a subclass of
that creates functions that can call any native function with a particular signature by passing an array of appropriately typed arguments (kind of like libffi
). You can compile a thunk builder object and then pass to it the address of a C function along with the array of arguments and it will call that function directly for you passing those arguments.
is an interface (abstract class) used to describe parts of a Virtual Machine (VM) architecture to JitBuilder so that it can interact with that state. A simple example would be a register holding some virtual state value like a program counter or stack pointer. A more complicated example would be the VM’s operand stack which is manipulated by the VM while simulating the execution of bytecodes. Naive JIT compilers replicate the steps to push and pop operands onto the virtual operand stack, even though the compiled code does not really need to use an operand stack and would benefit from using registers or local stack frame variables. The VirtualMachineOperandStack class allows a JitBuilder based JIT to push and pop IlValues just like the virtual machine pushes and pops actual values while executing bytecodes, but the code generated by JitBuilder will not actually use an operand stack. There are implementations of
for several kinds of state variables including an operand stack (
). This family of classes is typically used by JIT compiler implementations.
$ make g++ -o AtomicOperations.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/AtomicOperations.cpp g++ -g -fno-rtti -o atomicoperations AtomicOperations.o -L. -ljitbuilder -ldl g++ -o Call.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/Call.cpp g++ -g -fno-rtti -o call Call.o -L. -ljitbuilder -ldl g++ -o ConstString.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/ConstString.cpp g++ -g -fno-rtti -o conststring ConstString.o -L. -ljitbuilder -ldl g++ -o ControlFlowTests.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/ControlFlowTests.cpp g++ -o FieldAddress.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/FieldAddress.cpp g++ -g -fno-rtti -o fieldaddress FieldAddress.o -L. -ljitbuilder -ldl g++ -o IsSupportedType.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/IsSupportedType.cpp g++ -g -fno-rtti -o issupportedtype IsSupportedType.o -L. -ljitbuilder -ldl g++ -o IterativeFib.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/IterativeFib.cpp g++ -g -fno-rtti -o iterfib IterativeFib.o -L. -ljitbuilder -ldl g++ -o LinkedList.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/LinkedList.cpp g++ -g -fno-rtti -o linkedlist LinkedList.o -L. -ljitbuilder -ldl g++ -o LocalArray.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/LocalArray.cpp g++ -g -fno-rtti -o localarray LocalArray.o -L. -ljitbuilder -ldl g++ -o NestedLoop.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/NestedLoop.cpp g++ -g -fno-rtti -o nestedloop NestedLoop.o -L. -ljitbuilder -ldl g++ -o OperandStackTests.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/OperandStackTests.cpp g++ -g -fno-rtti -o operandstacktests OperandStackTests.o -L. -ljitbuilder -ldl g++ -o Pointer.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/Pointer.cpp g++ -g -fno-rtti -o pointer Pointer.o -L. -ljitbuilder -ldl g++ -o Pow2.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/Pow2.cpp g++ -g -fno-rtti -o pow2 Pow2.o -L. -ljitbuilder -ldl g++ -o RecursiveFib.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/RecursiveFib.cpp g++ -g -fno-rtti -o recfib RecursiveFib.o -L. -ljitbuilder -ldl g++ -o Simple.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/Simple.cpp g++ -g -fno-rtti -o simple Simple.o -L. -ljitbuilder -ldl g++ -o StructArray.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/StructArray.cpp g++ -g -fno-rtti -o structarray StructArray.o -L. -ljitbuilder -ldl g++ -o Switch.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/Switch.cpp g++ -g -fno-rtti -o switch Switch.o -L. -ljitbuilder -ldl g++ -o Thunk.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/Thunk.cpp g++ -g -fno-rtti -o thunks Thunk.o -L. -ljitbuilder -ldl g++ -o ToIlType.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/ToIlType.cpp g++ -g -fno-rtti -o toiltype ToIlType.o -L. -ljitbuilder -ldl g++ -o TransactionalOperations.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/TransactionalOperations.cpp g++ -g -fno-rtti -o transactionaloperations TransactionalOperations.o -L. -ljitbuilder -ldl g++ -o Union.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/Union.cpp g++ -g -fno-rtti -o union Union.o -L. -ljitbuilder -ldl g++ -o Worklist.o -g -std=c++0x -O2 -c -fno-rtti -fPIC -I./include/compiler -I./include src/Worklist.cpp g++ -g -fno-rtti -o worklist Worklist.o -L. -ljitbuilder -ldl
You’ve now built the tests! Not all of these tests, however, currently run on all platforms.
Running the 7 tests that should work everywhere is easy:
$ make test [ output of 7 tests omitted ]
The tests are:
IsSupportedTypeshows how to tell if a particular C or C++ type can be expressed as a JitBuilder type
IterativeFibis the code sample used in the second article in this series: it compiles a function that computes Fibonaccinumbers using an iterative algorithm.
NestedLoopis one of the language shootout benchmarks with a 6 deep loop nest containing a variable increment
Pow2is a trivial iterative approach to computing a power of 2
Simpleis the code sample from the [first article in this series](URL to first article): a function that simply returns its argument + 1
ToIlTypeshows how to use the toIlType template functions in TypeDictionary to map C/C++ types to TypeDictionary types
Worklistgives a (convoluted) example of using the worklist facility in MethodBuilder to traverse a flow graph of bytecodes
Not all the tests work on other platforms because JitBuilder is currently (a point in time limitation) missing automatic method trampolines for function calls on platforms with limited direct addressability. X86-64 and OS X, however, aren’t in this class of platforms, so you can actually run everything with ‘testall’ :
$ make testall [ output of 22 tests omitted ]
In addition to the code samples that run on all platforms, these samples include:
Callshowing how to call an arbitrary C function from a compiled method
ConstStringshows how to use ConstString to manipulate C strings
DotProductshows how to use arrays by computing a simple vector dot product
FieldAddressshows how to work with structs that are embedded in other structs
LinkedListimplements the lookup function for a linked list, showing how to work with structs and pointers
LocalArrayshows how to allocate arrays of data on the stack
OperandStackTestsdemonstrates the use of VirtualMachineOperandStack to simulate an operand stack without actually performing pushes and pops but being able to recreate the full operand stack if you need to call back into an interpreter
Pointerdemonstrates the use of pointers to several kinds of primitive data, including pointer to pointer to double
RecursiveFibcomputes Fibonacci numbers the traditional recursive way and shows that recursive calls are possible
StructArrayshows how to create arrays of structs
Switchdemonstrates how to specify a switch control flow structure
Thunkshows how to use ThunkBuilder to call native functions
Unionshows examples of how to represent and reference C union types and their fields using TypeDictionary
On a relatively modern x86-64 system (Linux or OS X) you can probably also run the tests targeting the experimental portion of the JitBuilder API:
$ make testexperimental [ output of 2 tests omitted ]
There are only two tests in this category so far:
This article showed you how to clone and configure the Eclipse OMR project, which contains enterprise quality components for building language runtimes. JitBuilder is a project inside Eclipse OMR, so the next step was to build that library from scratch. I explained all the high-level parts of the current JitBuilder API, and then brief descriptions for the 23 different code samples to help you learn how some of the more details aspects of the JitBuilder API work.
In the next article in this series, I’m going to show how to use JitBuilder to compile a small language, leveraging an existing set of tutorials (the Kaleidoscope tutorials) written for the LLVM compiler project . Many people ask how the Eclipse OMR project compares to LLVM, so the next article will look at some of the similarities and differences between LLVM and Eclipse OMR’s JitBuilder in the context of something you can do with both projects.