Just-In-Time Compilation (libgccjit.so)

This project aims aims to provide an embeddable shared library with an API for adding compilation to existing programs using GCC as the backend:libgccjit.so.

This shared library can then be dynamically-linked into bytecode interpreters and other such programs that want to generate machine code "on the fly" at run-time.

Status

libgccjit.so is currently an experimental prototype.

The maintainer is David Malcolm <dmalcolm@redhat.com>

Mailing list

There is a shared mailing list for both users and developers of the library: jit@gcc.gnu.org

You can subscribe by emailing jit-subscribe@gcc.gnu.org

The archives can be seen at: http://gcc.gnu.org/ml/jit/

Installation via packages

Fedora and RHEL

RPM packages of libgccjit (and its Python 2 and 3 bindings) are available for Fedora and RHEL, for i386 and x86_64.

These currently should be treated as experimental.

See https://copr.fedoraproject.org/coprs/dmalcolm/libgccjit/ for information on subscribing to the appropriate repository for your system. Having done this,

   1   sudo yum install libgccjit-devel

should give you both the JIT library (libgccjit) and the header files needed to develop against it (libgccjit-devel):

   1   $ rpm -qlv libgccjit
   2   lrwxrwxrwx    1 root    root       18 Aug 12 07:56 /usr/lib64/libgccjit.so.0 -> libgccjit.so.0.0.1
   3   -rwxr-xr-x    1 root    root 14463448 Aug 12 07:57 /usr/lib64/libgccjit.so.0.0.1
   4 
   5   $ rpm -qlv libgccjit-devel
   6   -rwxr-xr-x    1 root    root    37654 Aug 12 07:56 /usr/include/libgccjit++.h
   7   -rwxr-xr-x    1 root    root    28967 Aug 12 07:56 /usr/include/libgccjit.h
   8   lrwxrwxrwx    1 root    root       14 Aug 12 07:56 /usr/lib64/libgccjit.so -> libgccjit.so.0

Other distributions

Prebuilt packages for other distributions would be most welcome; please contact the jit mailing list.

Building it from source

The code can be seen within the git branch "dmalcolm/jit" here: http://gcc.gnu.org/git/?p=gcc.git;a=shortlog;h=refs/heads/dmalcolm/jit

Building from source currently requires about 4.2G of drive space (for the combination of the source tree, the build directory, and the installation path).

Checking out the code to "jit/src":

   1   mkdir jit
   2   cd jit
   3   git clone \
   4       -b dmalcolm/jit \
   5        git://gcc.gnu.org/git/gcc.git \
   6        src

The source tree currently occupies about 2.8G of disk space.

Building it within "jit/build":

   1   mkdir build
   2   cd build
   3   # We'll configure with --disable-bootstrap for speed:
   4   ../src/configure \
   5      --enable-host-shared \
   6      --enable-languages=jit \
   7      --disable-bootstrap
   8   nice make -j4 # altering the "4" to however many cores you have
   9 

Note that the default configuration enables lots of run-time self-checking, slowing things down. You may want to add

--enable-checking=release

to the configure invocation if you're profiling or benchmarking the code, which disables this self-checking.

On my 4-core laptop "make" takes 17 minutes and 1.1G of disk space (it's much faster with many cores and a corresponding -j setting).

This should build a libgccjit.so within jit/build/gcc:

   1   [build] $ file gcc/libgccjit.so*
   2   gcc/libgccjit.so:       symbolic link to `libgccjit.so.0'
   3   gcc/libgccjit.so.0:     symbolic link to `libgccjit.so.0.0.1'
   4   gcc/libgccjit.so.0.0.1: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped

Note that this is a branch of GCC, so if it fails to build, you might want to consult the general GCC FAQ for some common issues, before checking on the jit mailing list.

Running the testsuite (within "jit/build/gcc"), using -v for verbosity:

   1    [build] $ cd gcc
   2    [gcc] $ make check-jit RUNTESTFLAGS="-v -v -v"

A summary of the tests can then be seen in:

and detailed logs in:

The test executables can be seen as:

which can be run independently.

You can compile and run individual tests by passing "jit.exp=TESTNAME" to RUNTESTFLAGS e.g.:

   1    [gcc] $ make check-jit RUNTESTFLAGS="-v -v -v jit.exp=test-factorial.c"

and once a test has been compiled, you can debug it directly:

   1    [gcc] $ LD_LIBRARY_PATH=. gdb testsuite/jit/test-factorial.exe 

You should then be able to install it (to the --prefix specified earlier) via:

   1   make install

Installation uses a further 0.4G of disk space.

Sample client code

The API ("libgccjit.h") can be seen at http://gcc.gnu.org/git/?p=gcc.git;a=blob;f=gcc/jit/libgccjit.h;hb=dmalcolm/jit

The "main" function is in the client code. It uses a pure C API to call into libgccjit.so.

The API is very high-level, and is designed in terms of C semantics (since you're probably going to want to interface with C code).

   1 #include "libgccjit.h"
   2 
   3 int
   4 main (int argc, char **argv)
   5 {
   6   gcc_jit_context *ctxt;
   7   gcc_jit_result *result;
   8 
   9   /* Memory mangement is simple: all objects created are associated with a gcc_jit_context,
  10      and get automatically cleaned up when the context is released.  */
  11   ctxt = gcc_jit_context_acquire ();
  12 
  13   /* Let's inject the equivalent of:
  14          void
  15          some_fn (const char *name)
  16          {
  17             printf ("hello %s\n", name);
  18          }
  19      into the context.  */
  20   gcc_jit_type *void_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_VOID);
  21   gcc_jit_type *const_char_ptr_type = gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_CONST_CHAR_PTR);
  22   gcc_jit_param *param_name =
  23     gcc_jit_context_new_param (ctxt, NULL, const_char_ptr_type, "name");
  24   gcc_jit_function *func =
  25     gcc_jit_context_new_function (ctxt, NULL,
  26                                   GCC_JIT_FUNCTION_EXPORTED,
  27                                   void_type,
  28                                   "some_fn",
  29                                   1, &param_name,
  30                                   0);
  31 
  32   gcc_jit_param *param_format =
  33     gcc_jit_context_new_param (ctxt, NULL, const_char_ptr_type, "format");
  34   gcc_jit_function *printf_func =
  35     gcc_jit_context_new_function (ctxt, NULL,
  36                                   GCC_JIT_FUNCTION_IMPORTED,
  37                                   gcc_jit_context_get_type (ctxt, GCC_JIT_TYPE_INT),
  38                                   "printf",
  39                                   1, &param_format,
  40                                   1);
  41   gcc_jit_rvalue *args[2];
  42   args[0] = gcc_jit_context_new_string_literal (ctxt, "hello %s\n");
  43   args[1] = gcc_jit_param_as_rvalue (param_name);
  44 
  45   gcc_jit_block *block = gcc_jit_function_new_block (func, "initial");
  46   gcc_jit_block_add_eval (
  47     block, NULL,
  48     gcc_jit_context_new_call (ctxt,
  49                               NULL,
  50                               printf_func,
  51                               2, args));
  52   gcc_jit_block_end_with_void_return (block, NULL);
  53 
  54   /* OK, we're done populating the context.
  55 
  56      The next line actually calls into GCC and runs the build, all
  57      in a mutex for now, getting make a result object.  */
  58   result = gcc_jit_context_compile (ctxt);
  59     /* result is actually a wrapper around a DSO */  
  60 
  61   /* Now that we have result, we're done with ctxt.  Releasing it will
  62      automatically clean up all of the objects created within it.  */
  63   gcc_jit_context_release (ctxt);
  64 
  65   /* Look up a generated function by name, getting a void* back
  66      from the result object (pointing to the machine code), and
  67      cast it to the appropriate type for the function: */
  68   some_fn_type some_fn  = (some_fn_type)gcc_jit_result_get_code (result, "some_fn");
  69 
  70   /* We can now call the machine code: */       
  71   some_fn ("foo");
  72 
  73   /* Presumably we'd call it more than once.
  74      Once we're done with the code, this unloads the built DSO: */
  75   gcc_jit_result_release (result);
  76 }

For those that prefer C++, there's also a C++ wrapper API; see http://gcc.gnu.org/git/?p=gcc.git;a=blob_plain;f=gcc/jit/libgccjit%2B%2B.h;hb=refs/heads/dmalcolm/jit

The above example looks like this using the C++ API:

   1 #include "libgccjit++.h"
   2 
   3 int
   4 main (int argc, char **argv)
   5 {
   6   gccjit::context ctxt = gcc_jit_context_acquire ();
   7 
   8   /* Let's inject the equivalent of:
   9          void
  10          some_fn (const char *name)
  11          {
  12             printf ("hello %s\n", name);
  13          }
  14      into the context.  */
  15   gccjit::type void_type = ctxt.get_type (GCC_JIT_TYPE_VOID);
  16   gccjit::type const_char_ptr_type = ctxt.get_type (GCC_JIT_TYPE_CONST_CHAR_PTR);
  17   gccjit::param param_name = ctxt.new_param (const_char_ptr_type, "name");
  18   gccjit::function func =
  19     ctxt.new_function (GCC_JIT_FUNCTION_EXPORTED,
  20                        void_type,
  21                        "some_fn",
  22                        std::vector<gccjit::param> (1, param_name),
  23                        0);
  24 
  25   gccjit::param param_format = ctxt.new_param (const_char_ptr_type, "format");
  26   gccjit::function printf_func =
  27     ctxt.new_function (GCC_JIT_FUNCTION_IMPORTED,
  28                        ctxt.get_type (GCC_JIT_TYPE_INT),
  29                        "printf",
  30                        std::vector<gccjit::param> (1, param_format));
  31 
  32   gccjit::block block = func.new_block ("initial");
  33   block.add_eval (ctxt.new_call (printf_func,
  34                                  ctxt.new_rvalue ("hello %s\n"),
  35                                  param_name));
  36   block.end_with_return ();
  37 
  38   /* OK, we're done populating the context.  */
  39 
  40   gccjit::result result = gcc_jit_context_compile (ctxt);
  41 
  42   ctxt.release ();
  43 
  44   /* Look up a generated function by name, getting a void* back
  45      from the result object (pointing to the machine code), and
  46      cast it to the appropriate type for the function: */
  47   some_fn_type some_fn  = (some_fn_type)gcc_jit_result_get_code (result, "some_fn");
  48 
  49   /* We can now call the machine code: */       
  50   some_fn ("foo");
  51 
  52   result.release ();
  53 }

Who's using this code?

Various caveats

Architecture

The idea is that GCC is configured with a special --enable-host-shared option, which leads to it being built as position-independent code. You would configure it with host==target, given that the generated machine code will be executed within the same process (the whole point of JIT).

libgccjit.so is built against libbackend.a. To the rest of GCC, it looks like a "frontend" (in the "gcc/jit" subdir), but the parsing hook works by replaying a record of the API calls made by client code.

You can see a diagram of how it all fits together at: http://gcc.gnu.org/git/?p=gcc.git;a=blob;f=gcc/jit/notes.txt;hb=refs/heads/dmalcolm/jit

The jit "frontend" requires --enable-host-shared, so it is off by default, so you need to configure with:

to get the jit (and see caveats above).

There are some major kludges in there, but it does work: it can successfully build code in-process 1000 times in a row albeit with a slow memory leak, with all optimization turned off. Upon turning on optimizations I run into crashes owing to not properly purging all state within the compiler - so this is a great motivation for doing more state-cleanup work. I've also hacked timevars to run in "cumulative" mode, accumulating all timings across all iterations.

The library API hides GCC's internals, and tries to be much more typesafe than GCC's, giving something rather like Andrew MacLeod's proposed changes - client code does not see "tree", instead dealing with types, rvalues, lvalues, basic blocks, etc. It is pure C, given the horror stories I have heard about people dealing with C++ ABIs.

The API deliberately uses C terminology, given that it's likely that the user will want to be plugging the JIT-generated code into a C/C++ program (or library).

News

None: JIT (last edited 2014-10-20 20:54:12 by DavidMalcolm)