tshort / StaticCompiler.jl

Compiles Julia code to a standalone library (experimental)
Other
497 stars 30 forks source link

StaticCompiler

CI CI (Integration) CI (Julia nightly) CI (Integration nightly) Coverage

This is an experimental package to compile Julia code to standalone libraries. A system image is not needed.

Installation and Usage

Installation is the same as any other registered Julia package

using Pkg
Pkg.add("StaticCompiler")

Standalone compilation

StaticCompiler.jl provides the functions compile_executable and compile_shlib for compiling a Julia function to a native executable or shared library for use from outside of Julia:

julia> using StaticCompiler, StaticTools

julia> hello() = println(c"Hello, world!")
hello (generic function with 1 method)

julia> compile_executable(hello, (), "./")
"/Users/user/hello"

shell> ls -alh hello
-rwxrwxr-x. 1 user user 8.4K Oct 20 20:36 hello

shell> ./hello
Hello, world!

This approach comes with substantial limitations compared to regular julia code, as you cannot rely on julia's runtime, libjulia (see, e.g., StaticTools.jl for some ways to work around these limitations).

The low-level function StaticCompiler.generate_obj (not exported) generates object files. This can be used for more control of compilation. This can be used for example, to cross-compile to other targets.

Method overlays

Sometimes, a julia function you want to statically compile will do things (such as throwing errors) that aren't supported natively by StaticCompiler. One tool provided for working around this is the @device_override macro which lets you swap out a method, but only inside of a StaticCompiler.jl compilation context. For example:

julia> using Libdl, StaticCompiler

julia> f(x) = g(x) + 1;

julia> g(x) = 2x

julia> @device_override g(x::Int) = x - 10

julia> f(1) # Gives the expected answer in regular julia
3

julia> dlopen(compile_shlib(f, (Int,), "./")) do lib
           fptr = dlsym(lib, "f")
           # Now use the compiled version where g(x) = 2x is replaced with g(x) = x - 10
           @ccall $fptr(1::Int)::Int 
       end
-8

Typically, errors should be overrided and replaced with @print_and_throw, which is StaticCompiler friendly, i.e. we define overrides such as

@device_override @noinline Base.Math.throw_complex_domainerror(f::Symbol, x) =
    @print_and_throw c"This operation requires a complex input to return a complex result"

If for some reason, you wish to use a different method table (defined with Base.Experimental.@MethodTable and Base.Experimental.@overlay) than the default one provided by StaticCompiler.jl, you can provide it to compile_executable and compile_shlib via a keyword argument method_table.

Approach

This package uses the GPUCompiler package to generate code.

Limitations

Guide for Package Authors

To enable code to be statically compiled, consider the following:

Guide for Statically Compiling Code

If you're trying to statically compile generic code, you may run into issues if that code uses features not supported by StaticCompiler. One option is to change the code you're calling using the tips above. If that is not easy, you may by able to compile it anyway. One option is to use method overlays to change what methods are called.

Cthulhu is a great help in digging into code, finding type instabilities, and finding other sources of code that may break static compilation.

Foreign Function Interfacing

Because Julia objects follow C memory layouts, compiled libraries should be usable from most languages that can interface with C. For example, results should be usable with Python's CFFI package.

For WebAssembly, interface helpers are available at WebAssemblyInterfaces, and users should also see WebAssemblyCompiler for a package more focused on compilation of WebAssebly in general.