Hachi

We do security research on Cardano


Compiling Plutus Core to LLVM

Contents

Today we are announcing plc-llvm, our compiler from Plutus Core (PLC) to the LLVM intermediate representation (LLVM IR). LLVM is a framework for code generation, optimisation, and analysis. While Plutus Core programs are ordinarily interpreted by Cardano Nodes using a program written in Haskell, plc-llvm allows us to generate native executables and libraries from Plutus Core programs. Additionally, we will be able to take advantage of existing LLVM tooling to analyse the LLVM IR that is generated by our compiler.

Compiling and running PLC programs

The quickest way to get started is using the Docker image or using one of our binary releases, but you can also build the compiler from source. We do not currently provide pre-built binaries for Windows. If you plan to use plc-llvm on Windows, we recommend that you use the Docker image.

Suppose that we have a simple, Untyped Plutus Core program such as the following and save it as twelve.plc:

(program 1.0.0
    [[(builtin addInteger) (con integer 4)] (con integer 8)]
)

We can then compile this using plc-llvm by running the following command. If you are using the Docker image, substitute plc-llvm for docker run --rm -v $PWD:/data ghcr.io/hachisecurity/plc-llvm:latest in all examples and prefix the filenames with /data/.

$ plc-llvm twelve.plc

This will generate an executable file named twelve in the same directory as the source file. You can then run that executable (e.g. with ./twelve). The output should be as expected:

12

If the input code is written in Typed Plutus Core, specifying the --typed flag will tell the compiler to treat the input accordingly. If the input is in the serialised, binary representation rather than textual one, the input can be deserialised by specifying the --deserialise flag.

Calling PLC from C

It is likely that the Plutus Core program you want to run does not have all the arguments it needs to produce a meaningful result. For example, consider the following Untyped Plutus Core program which we save as addition.plc:

(program 1.0.0
    (lam x [[(builtin addInteger) (con integer 4)] x])
)

Here we require an argument for x before we can compute the result of the addition. We can compile this to an object file which can then be linked together with code written in other languages by running the following command:

$ plc-llvm addition.plc --library

This will produce an object file addition.o and a C header file addition.h. We can include the header file in other C sources and link addition.o into other programs. For example, to consume our addition program from above, we could write a C source file that includes the addition.h header:

#include "addition.h"

int main() {
    // allocate a new integer closure, this will be our extra argument
    closure *arg = plc_new_integer("104");

    // run the PLC program and acquire a pointer to the resulting closure
    closure *ptr = plc_entry();

    // apply the resulting closure to the extra argument, which also returns a closure
    closure *res = plc_apply(ptr, arg);

    // invoke the closure's pretty-printing function
    plc_print_closure(res);
    putchar('\n');

    return 0;
}

Suppose that the above code is saved to a file named program.c, then we can compile it into an executable with e.g. the following command:

$ clang addition.o program.c rts/rts.c -lgmp -lsodium -o addition

This will produce an executable named addition which we can invoke with ./addition and which produces the result we would expect:

108

For convenience, plc-llvm can automate all of the above steps for us as long as we write the C code and tell plc-llvm where to find it. We can do this with the following command. Note that this recompiles addition.plc so that addition.o and addition.h get updated if there have been any changes:

$ plc-llvm addition.plc --library --entry-point program.c

As before, this will produce an executable named addition which we can invoke with e.g. ./addition.

Providing such a straight-forward interface with C programs allows us to invoke PLC programs with arbitrary arguments that may come from e.g. symbolic execution or fuzzing frameworks.

Conclusions

plc-llvm can compile arbitrary Plutus Core (PLC) programs, such as those stored on the Cardano blockchain, into native machine code using the LLVM framework. Our compiler can compile any given PLC program and supports the entire PLC standard library.

However, being able to turn PLC programs into native code is only the first step for us. By targeting LLVM IR as an intermediate language, we also gain access to all existing tooling for LLVM and can perform code generation for all architectures that are supported by LLVM. Our next goals with plc-llvm are to support the addition of debugging metadata to allow tools such as lldb to produce more meaning diagnostics as well as to support the KLEE symbolic execution engine out-of-the-box.

Hachi is a research effort in security, programming language theory, and formal methods on the Cardano blockchain. Follow our blog at blog.hachi.one for frequently updated content in this exciting field.