Running LLVM on GraalVM

GraalVM provides an implementation of the lli tool to directly execute programs from LLVM bitcode.

In contrast to static compilation that is normally used for LLVM based languages, LLI first interprets the bitcode and then dynamically compiles the hot parts of the program using the Graal compiler. This allows seamless interoperability with the dynamic languages supported by GraalVM.

Run programs in LLVM bitcode format:

lli [LLI Options] [GraalVM Options] [Polyglot Options] filename.bc [program args]

Where filename.bc is a single program source file in LLVM bitcode format.

Note: LLVM bitcode is platform dependent. The program must be compiled to bitcode for the appropriate platform.

Compiling to LLVM Bitcode

GraalVM can execute C/C++, Fortran, and other languages that can be compiled to LLVM bitcode. As a first step, you have to compile the program to LLVM bitcode using an LLVM frontend such as clang. C/C++ code can be compiled to LLVM bitcode using clang with the -emit-llvm option.

Here is some example C code named hello.c:

#include <stdio.h>

int main() {
    printf("Hello from GraalVM!\n");
    return 0;

You can compile hello.c to an LLVM bitcode file named hello.bc as follows:

$ clang -c -O1 -emit-llvm hello.c

You can then run hello.bc on GraalVM like this:

$ lli hello.bc
Hello from GraalVM!

External library dependencies

If the bitcode file depends on external libraries, they can be loaded using the --lib argument.

For example:

#include <unistd.h>
#include <ncurses.h>

int main() {
    printw("Hello, Curses!");
    return 0;

This can be run with:

$ clang -c -O1 -emit-llvm hello-curses.c
$ lli --lib /usr/lib/ hello-curses.bc

For Mac OS users, ncurses library path is /usr/lib/libncurses.dylib.

Running C++

For running C++ code, LLI requires the libc++ standard library from the LLVM project. If you are using Ubuntu Linux, install the package libc++1.

#include <iostream>

int main() {
    std::cout << "Hello, C++ World!" << std::endl;

Make sure you compile your C++ code with the correct standard library:

$ clang++ -c -O1 -emit-llvm -stdlib=libc++ hello-c++.cpp
$ lli hello-c++.bc
Hello, C++ World!

Running Rust

LLI doesn’t load the Rust standard libraries automatically. To install Rust, run the following in your terminal, then follow the onscreen instructions:

curl -sSf | sh

To run Rust code, the required Rust libraries have to be specified manually.

fn main() {
    println!("Hello Rust!");

This can be run with:

$ rustc --emit=llvm-bc
$ lli --lib $(rustc --print sysroot)/lib/libstd-* hello-rust.bc
Hello Rust!


GraalVM supports several other programming languages, including JavaScript, Python, Ruby, and R. While LLI is designed to run LLVM bitcode, it also provides an API for programming language interoperability that lets you execute code from any other language that GraalVM supports.

Dynamic languages like JavaScript usually access object members by name. Since normally names are not preserved in LLVM bitcode, it must be compiled with debug info enabled.

The following example demonstrates how you can use the API for interoperability with other programming languages.

Let’s define a C struct for points and implement allocation functions:

// cpart.c
#include <polyglot.h>

#include <stdlib.h>
#include <stdio.h>

struct Point {
    double x;
    double y;


void *allocNativePoint() {
    struct Point *ret = malloc(sizeof(*ret));
    return polyglot_from_Point(ret);

void *allocNativePointArray(int length) {
    struct Point *ret = calloc(length, sizeof(*ret));
    return polyglot_from_Point_array(ret, length);

void freeNativePoint(struct Point *p) {

void printPoint(struct Point *p) {
    printf("Point<%f,%f>\n", p->x, p->y);

Make sure GRAALVM_HOME resolves to the GraalVM installation directory, then compile the cpart.c file with:

$ clang -g -O1 -c -emit-llvm -I$GRAALVM_HOME/jre/languages/llvm cpart.c

You can access your C/C++ code from other languages like JavaScript:

// jspart.js

// Load and parse the LLVM bitcode into GraalVM
var cpart = Polyglot.evalFile("llvm" ,"cpart.bc");

// Allocate a light-weight C struct
var point = cpart.allocNativePoint();

// Access it as if it were a JS object
point.x = 5;
point.y = 7;

// Pass it back to a native function

// We can also allocate an array of structs
var pointArray = cpart.allocNativePointArray(15);

// We can access this array like it was a JS array
for (var i = 0; i < pointArray.length; i++) {
    var p = pointArray[i];
    p.x = i;
    p.y = 2*i;


// We can also pass a JS object to a native function
cpart.printPoint({x: 17, y: 42});

// Don't forget to free the unmanaged data objects

Run this JavaScript file with:

$ js --polyglot jspart.js

Polyglot C API

There are also lower level API functions for directly accessing polyglot values from C. See the Polyglot Reference and the documentation comments in polyglot.h for more details.

For example, this program allocates and accesses a Java array from C:

#include <stdio.h>
#include <polyglot.h>

int main() {
    void *arrayType = polyglot_java_type("int[]");
    void *array = polyglot_new_instance(arrayType, 4);
    polyglot_set_array_element(array, 2, 24);
    int element = polyglot_as_i32(polyglot_get_array_element(array, 2));
    printf("%d\n", element);
    return element;

Compile it to LLVM bitcode:

$ clang -g -O1 -c -emit-llvm -I$GRAALVM_HOME/jre/languages/llvm polyglot.c

And run it, using the --jvm argument to run GraalVM in JVM mode, since we are using a Java type:

$ lli --jvm polyglot.bc

Embedding in Java

GraalVM can also be used to embed LLVM bitcode in Java host programs.

For example, let’s write a Java class that embeds GraalVM to run the previous example:

import org.graalvm.polyglot.*;

class Polyglot {
    public static void main(String[] args) throws IOException {
        Context polyglot = Context.newBuilder().
        File file = new File("polyglot.bc");
        Source source = Source.newBuilder("llvm", file).build();
        Value cpart = polyglot.eval(source);

Compiling and running it:

$ javac
$ java Polyglot

See the Embedding documentation for more information.

Source-Level Debugging

You can use GraalVM’s Debugger to debug the program you compiled to LLVM bitcode. To use this feature, please make sure to compile your program with debug information by specifying the -g argument when compiling with clang. This gives you the ability to step through the program’s source code and set breakpoints in it. To also be able to inspect the local and global variables of your program you may pass --llvm.enableLVI=true as argument to lli. This option is not enabled per default as it can significantly decrease your program’s run-time performance.

LLVM Compatibility

GraalVM works with LLVM bitcode versions 3.8 to 7.0.

Optimization flags

In contrast to the static compilation model of LLVM languages, in GraalVM the machine code is not directly produced from the LLVM bitcode, but there is an additional dynamic compilation step by the Graal compiler.

In this scenario, first the LLVM frontend (e.g. clang) does optimizations on the bitcode level, and then Graal does its own optimizations on top of that during dynamic compilation. Some optimizations are better when done ahead-of-time on the bitcode, while other optimizations are better left for the dynamic compilation of Graal, when profiling information is available.

In principle, all optimization levels should work, but for best results we suggest compiling the bitcode with optimization level -O1.

Cross-language interoperability will only work when the bitcode is compiled with debug information enabled (-g), and the -mem2reg optimization is performed on the bitcode (compiled with at least -O1, or explicitly using the opt tool).

LLI Command Options

-L <path>/--llvm.libraryPath=<path>: a list of paths where GraalVM will search for library dependencies. Paths are delimited by :.

--lib <libs>/--llvm.libraries=<libs>: a list of libraries to load. The list can contain precompiled native libraries (*.so/*.dylib) and bitcode libraries (*.bc). Files with a relative path are looked up relative to llvm.libraryPath. Entries are delimited by :.

--llvm.enableLVI=<true/false>: enable source-level symbol inspection in the debugger. This defaults to false as it can decrease run-time performance.

--llvm.sandboxed enables an experimental sandboxed mode, which means memory allocations from LLVM bitcode are done on the managed heap. This article explains the sandboxed execution in every detail.

--version prints the version and exit.

--version:graalvm prints GraalVM version information and exit.

Expert and Diagnostic Options

Use --help and --help:<topic> to get a full list of options.