Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static compilation to WebAssembly #5

Open
tshort opened this issue Jan 6, 2019 · 3 comments
Open

Static compilation to WebAssembly #5

tshort opened this issue Jan 6, 2019 · 3 comments

Comments

@tshort
Copy link
Collaborator

tshort commented Jan 6, 2019

Ahead-of-time static compilation to WebAssembly would enable smaller downloads to execute Julia code in the browser. We now have a libjulia that compiles nicely to WebAssembly. How to generate code to link to that is uncertain. Some options include:

  1. PackageCompiler style--Here, Julia's --output-bc option is used to create LLVM IR with the wasm32-unknown-unknown triple. This approach is currently really slow for native code generation because it recompiles everything starting from base/sysimg.jl. If code generation can be enabled in the wasm version of Julia, this approach could be done in the browser to generate wasm-compatible code. Emscripten would be used for final compilation/linking to WebAssembly. Right now, Emscripten chokes on the addrspaces that Julia generates.

  2. CUDAnative style--In this approach, LLVM IR modules are created for Julia code, similar to how @code_llvm works. This works well for simple code, and it should be fast. It doesn't currently work for recursive code. It also doesn't work well for code that calls libjulia functions. foreigncalls are often converted to calls to raw addresses, and it also has the addrspace issue.

  3. Charlotte--By directly compiling Julia's IR to WebAssembly, it is possible to customize the compilation. This should be fast and flexible. The downsides of this approach are: no LLVM optimizations and no automatic linking (LLD is supposed to be able to link wasm files).

@tshort
Copy link
Collaborator Author

tshort commented Feb 23, 2019

For static compilation, I'm looking for feedback on an approach that adds another compilation mode to Julia. It'd be somewhat like imaging_mode, but it'd be meant to work with a CUDAnative-style compilation. Here's a starting point:

JuliaLang/julia@jn/codegen-norecursion...tshort:jn/codegen-norecursion

I used @vtjnash's jn/codegen-norecursion branch as a starting point. That looks like the future of AOT compilation. The idea is to link the resulting bitcode with libjulia. Handling ccall looks pretty easy. Handling global variables looks hard.

Does this look like a good way to go for AOT compilation of wasm? Any things to watch out for?

Here's code I used to output a compiled LLVM module (mostly from CUDAnative):

import LLVM

function irgen(@nospecialize(f), @nospecialize(tt))
    # get the method instance
    world = typemax(UInt)
    meth = which(f, tt)
    sig_tt = Tuple{typeof(f), tt.parameters...}
    (ti, env) = ccall(:jl_type_intersection_with_env, Any,
                      (Any, Any), sig_tt, meth.sig)::Core.SimpleVector
    meth = Base.func_for_method_checked(meth, ti)
    linfo = ccall(:jl_specializations_get_linfo, Ref{Core.MethodInstance},
                  (Any, Any, Any, UInt), meth, ti, env, world)

    # set-up the compiler interface
    function hook_raise_exception(insblock::Ptr{Cvoid}, ex::Ptr{Cvoid})
        insblock = convert(LLVM.API.LLVMValueRef, insblock)
        ex = convert(LLVM.API.LLVMValueRef, ex)
        raise_exception(BasicBlock(insblock), Value(ex))
    end
    params = Base.CodegenParams(track_allocations=false,
                                code_coverage=false,
                                static_alloc=false,
                                prefer_specsig=true,
                                raise_exception=hook_raise_exception)

    # generate IR
    ccall(:jl_set_standalone_aot_mode, Nothing, ())
    native_code = ccall(:jl_create_native, Ptr{Cvoid},
                        (Vector{Core.MethodInstance}, Base.CodegenParams), [linfo], params)
    @assert native_code != C_NULL
    llvm_mod_ref = ccall(:jl_get_llvm_module, LLVM.API.LLVMModuleRef,
                         (Ptr{Cvoid},), native_code)
    @assert llvm_mod_ref != C_NULL
    ccall(:jl_clear_standalone_aot_mode, Nothing, ())
    LLVM.Module(llvm_mod_ref)
end

f1() = ccall("myfun", Int, ())
m1 = irgen(f1, Tuple{})
f2() = cglobal("myglobal", Int)
m2 = irgen(f2, Tuple{})

@tshort
Copy link
Collaborator Author

tshort commented Mar 1, 2019

I've made a bit more progress on a "standalone_aot_mode". Updates here. Some items are straightforward:

  • Making ccall and cglobal emit references to external symbols
  • Converting builtin Julia types to an external symbol, so UInt32 becomes jl_uint32_type

What I'm struggling with is how to handle global variables. For generated types and other things that are immutable, it's probably possible to create LLVM global variables for these. For general mutable objects, I'm more confused. Should I try to re-use Julia's sysimg machinery? I don't want the whole sysimg, only the parts the exported functions use.

@tshort
Copy link
Collaborator Author

tshort commented Apr 5, 2019

I've made more progress on a "standalone_aot_mode". See the following for a description and some test files, so you can get an idea of what works:

https://github.com/tshort/julia/tree/standalone-mode/test/standalone-aot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant