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

Windows support + Stability #3

Merged
merged 36 commits into from Dec 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f55f261
Adding Windows definition for :Sleep
aminya Nov 30, 2019
fb2feb0
Checking for gcc installation
aminya Nov 30, 2019
62218f3
Runcommand based on OS
aminya Nov 30, 2019
3329158
Minor code formatting
aminya Nov 30, 2019
74b5070
Travis Windows and Julia 1.3
aminya Nov 30, 2019
5a7c8b1
Appveyor Julia 1.3
aminya Nov 30, 2019
01b0a97
Moving function definition out of testset macro
aminya Nov 30, 2019
db78fd0
Add @jlrun test for f1,f2,f3
aminya Nov 30, 2019
945be2c
verify generated IR for f()
aminya Nov 30, 2019
b39a16f
First checking gcc installation
aminya Nov 30, 2019
8737279
Separate libname for windows and linux
aminya Nov 30, 2019
eefed62
better libdir and bindir
aminya Nov 30, 2019
fc930f6
Adding includedir
aminya Nov 30, 2019
d0a583b
adding julia bindir to path on windows
aminya Nov 30, 2019
64ef80b
Ordering issue
aminya Nov 30, 2019
93dfa92
Test dep
aminya Dec 1, 2019
8693bb4
[skip ci] minor formatting
aminya Dec 1, 2019
25ecbed
includedir removal
aminya Dec 1, 2019
eee033e
libname removal
aminya Dec 1, 2019
b6808f1
Adding Julia to the path removal
aminya Dec 1, 2019
65e1834
formatting using JuliaFormatter
aminya Dec 1, 2019
c04d580
gitignore:
aminya Dec 1, 2019
b803652
Windows support in the readme
aminya Dec 1, 2019
9365109
Fixes tshort#5
aminya Dec 1, 2019
a93c3f8
jl_erno instead of usleep
aminya Dec 1, 2019
e1795ae
Another deprecation fix
aminya Dec 1, 2019
e428040
Full Cmap
aminya Dec 1, 2019
2f4285c
Full cformat map
aminya Dec 2, 2019
ac19452
Fixes for standalone exe on Windows
aminya Dec 2, 2019
41188be
commenting duplicates in Cmap
aminya Dec 2, 2019
3c8537b
Bug fix attempt
aminya Dec 2, 2019
03d3833
Shellcmd based on OS
aminya Dec 2, 2019
a6a12c6
OS specific path handeling
aminya Dec 2, 2019
1fa6b0c
correct standalone dir
aminya Dec 2, 2019
93282a0
formatting
aminya Dec 2, 2019
3c7ed69
[skip ci] Remove trailing spaces
aminya Dec 2, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .appveyor.yml
@@ -1,7 +1,7 @@
# Documentation: https://github.com/JuliaCI/Appveyor.jl
environment:
matrix:
- julia_version: 1.2
- julia_version: 1.3
- julia_version: nightly
platform:
- x86
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Expand Up @@ -6,4 +6,9 @@
/dev/
/test/standalone
/test/Manifest.toml
/test/test.*
/test/test.*

test.o
test.so
test.bc
/standalone/*
3 changes: 2 additions & 1 deletion .travis.yml
Expand Up @@ -3,8 +3,9 @@ language: julia
os:
- linux
- osx
- windows
julia:
- 1.2
- 1.3
- nightly
matrix:
allow_failures:
Expand Down
1 change: 1 addition & 0 deletions Project.toml
Expand Up @@ -5,6 +5,7 @@ version = "0.1.0"

[deps]
Cassette = "7057c7e9-c182-5462-911a-8362d720325c"
Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0"
LLVM = "929cbde3-209d-540e-8aea-75f648917ca0"
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
Expand Down
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -5,9 +5,9 @@
[![Codecov](https://codecov.io/gh/tshort/StaticCompiler.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/tshort/StaticCompiler.jl)
[![Coveralls](https://coveralls.io/repos/github/tshort/StaticCompiler.jl/badge.svg?branch=master)](https://coveralls.io/github/tshort/StaticCompiler.jl?branch=master)

This is an experimental package to compile Julia code to standalone libraries. A system image is not needed. It is also meant for cross compilation, so Julia code can be compiled for other targets, including WebAssembly and embedded targets.
This is an experimental package to compile Julia code to standalone libraries. A system image is not needed. It is also meant for cross compilation, so Julia code can be compiled for other targets, including WebAssembly and embedded targets.

Long term, a better approach may be to use Julia's standard compilation techniques with "tree shaking" to generate a reduced system image (see [here](https://github.com/JuliaLang/julia/issues/33670)).
Long term, a better approach may be to use Julia's standard compilation techniques with "tree shaking" to generate a reduced system image (see [here](https://github.com/JuliaLang/julia/issues/33670)).

This package uses the [LLVM package](https://github.com/maleadt/LLVM.jl) to generate code in the same fashion as [CUDAnative](https://github.com/JuliaGPU/CUDAnative.jl).

Expand All @@ -30,7 +30,7 @@ write(m, "cos.bc")
write_object(m, "cos.o")
```

`cos.o` should contain a function called `cos`. From there, you need to convert to link as needed with `libjulia`.
`cos.o` should contain a function called `cos`. From there, you need to convert to link as needed with `libjulia`.

See the `test` directory for more information and types of code that currently run. The most advanced example that works is a call to an ODE solution using modified code from [ODE.jl](https://github.com/JuliaDiffEq/ODE.jl). For information on compiling and linking to an executable, see [test/standalone-exe.jl](./blob/master/test/standalone-exe.jl).

Expand All @@ -44,6 +44,6 @@ See the `test` directory for more information and types of code that currently r

* The use of Cassette makes it more difficult for Julia to infer some things, and only type-stable code can be statically compiled with this approach.

* It's only been tested on Linux.
* It's only been tested on Linux and Windows.

Finally, this whole approach is young and likely brittle. Do not expect it to work for your code.
4 changes: 2 additions & 2 deletions src/ccalls.jl
Expand Up @@ -44,7 +44,7 @@ iscglobal(x) = x == cglobal || x isa GlobalRef && x.name == :cglobal
"""
fix_ccalls!(mod::LLVM.Module, d)

Replace function addresses with symbol names in `mod`. The symbol names are
Replace function addresses with symbol names in `mod`. The symbol names are
meant to be linked to `libjulia` or other libraries.
`d` is a `Dict` mapping a function address to symbol name for `ccall`s.
"""
Expand All @@ -70,7 +70,7 @@ function fix_ccalls!(mod::LLVM.Module, d)
# dest = called_value(instr)
for op in operands(instr)
lastop = op
if occursin("inttoptr", string(op))
if occursin("inttoptr", string(op))
# @show instr
if occursin("addrspacecast", string(op)) || occursin("getelementptr", string(op))
op = first(operands(op))
Expand Down
11 changes: 5 additions & 6 deletions src/globals.jl
Expand Up @@ -15,7 +15,7 @@ For each global variable, two LLVM global objects are created:

The `inttopt` with the function address is replaced by `jl.global`.

A function `jl_init_globals` is added to `mod`. This function deserializes the data in
A function `jl_init_globals` is added to `mod`. This function deserializes the data in
`jl.global.data` and updates `jl.global`.
"""

Expand Down Expand Up @@ -83,7 +83,7 @@ function fix_globals!(mod::LLVM.Module)
unsafe_delete!(mod, fun)
continue
end

for blk in blocks(fun), instr in instructions(blk)
# Set up functions to walk the operands of the instruction
# and convert appropriate ConstantExpr's to instructions.
Expand Down Expand Up @@ -129,7 +129,7 @@ function fix_globals!(mod::LLVM.Module)
data = LLVM.GlobalVariable(mod, gv_typ, "jl.global.data")
linkage!(data, LLVM.API.LLVMExternalLinkage)
constant!(data, true)
LLVM.API.LLVMSetInitializer(LLVM.ref(data),
LLVM.API.LLVMSetInitializer(LLVM.ref(data),
LLVM.API.LLVMConstArray(LLVM.ref(uint8_t),
[LLVM.ref(ConstantInt(uint8_t, x)) for x in v],
UInt32(length(v))))
Expand All @@ -138,7 +138,7 @@ function fix_globals!(mod::LLVM.Module)

# Create the Julia object from `data` and include that in `init_fun`.
position!(builder, jl_init_global_entry)
gfunc_type = LLVM.FunctionType(julia_to_llvm(Cvoid),
gfunc_type = LLVM.FunctionType(julia_to_llvm(Cvoid),
LLVMType[LLVM.PointerType(julia_to_llvm(Int8)),
Iterators.repeated(LLVM.FunctionType(julia_to_llvm(Any)), nglobals)...])
deserialize_globals_func = LLVM.Function(mod, "_deserialize_globals", gfunc_type)
Expand All @@ -151,7 +151,7 @@ function fix_globals!(mod::LLVM.Module)
ret!(builder)
end
tt = Tuple{Ptr{UInt8}, Iterators.repeated(Ptr{Any}, nglobals)...}
deser_mod = irgen(deser_fun, tt, overdub = false)
deser_mod = irgen(deser_fun, tt, overdub = false)
d = find_ccalls(deser_fun, tt)
fix_ccalls!(deser_mod, d)
# rename deserialization function to "_deserialize_globals"
Expand All @@ -162,4 +162,3 @@ function fix_globals!(mod::LLVM.Module)
LLVM.link!(mod, deser_mod)
return
end

19 changes: 15 additions & 4 deletions src/irgen.jl
Expand Up @@ -11,7 +11,13 @@ function xlinfo(f, tt)
sig_tt = Tuple{typeof(g), 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)

if VERSION >= v"1.2.0-DEV.320"
meth = Base.func_for_method_checked(meth, ti, env)
else
meth = Base.func_for_method_checked(meth, ti)
end

return ccall(:jl_specializations_get_linfo, Ref{Core.MethodInstance},
(Any, Any, Any, UInt), meth, ti, env, world)
end
Expand All @@ -35,7 +41,7 @@ end
Generates Julia IR targeted for static compilation.
`ccall` and `cglobal` uses have pointer references changed to symbols
meant to be linked with libjulia and other libraries.
If `overdub == true` (the default), Cassette is used to swap out
If `overdub == true` (the default), Cassette is used to swap out
`ccall`s with a tuple of library and symbol.
"""
function irgen(@nospecialize(func), @nospecialize(tt); optimize = true, overdub = true)
Expand All @@ -47,7 +53,13 @@ function irgen(@nospecialize(func), @nospecialize(tt); optimize = true, overdub
sig_tt = Tuple{typeof(gfunc), 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)

if VERSION >= v"1.2.0-DEV.320"
meth = Base.func_for_method_checked(meth, ti, env)
else
meth = Base.func_for_method_checked(meth, ti)
end

linfo = ccall(:jl_specializations_get_linfo, Ref{Core.MethodInstance},
(Any, Any, Any, UInt), meth, ti, env, world)

Expand Down Expand Up @@ -192,4 +204,3 @@ function write_object(mod::LLVM.Module, path)
emit(tm, mod, LLVM.API.LLVMObjectFile, path)
end
end

3 changes: 1 addition & 2 deletions src/overdub.jl
Expand Up @@ -8,7 +8,7 @@ using Cassette
function transform(ctx, ref)
CI = ref.code_info
ismatch = x -> begin
Base.Meta.isexpr(x, :foreigncall) &&
Base.Meta.isexpr(x, :foreigncall) &&
Base.Meta.isexpr(x.args[1], :call)
end
replace = x -> begin
Expand All @@ -31,4 +31,3 @@ const ctx = Cassette.disablehooks(Ctx(pass = Pass))
#@inline Cassette.overdub(ctx::Ctx, ::typeof(+), a::T, b::T) where T<:Union{Float32, Float64} = add_float_contract(a, b)

contextualize(f::F) where F = (args...) -> Cassette.overdub(ctx, f, args...)

12 changes: 6 additions & 6 deletions src/serialize.jl
@@ -1,7 +1,7 @@

"""
A context structure for holding state related to serializing Julia
objects. A key component is an `IOBuffer` used to hold the serialized
objects. A key component is an `IOBuffer` used to hold the serialized
result.
"""
struct SerializeContext
Expand Down Expand Up @@ -61,7 +61,7 @@ end
"""
serialize(ctx::SerializeContext, x)

Serialize `x` into the context object `ctx`. `ctx.io` is the `IOBuffer` where the
Serialize `x` into the context object `ctx`. `ctx.io` is the `IOBuffer` where the
serialized results are stored. Get the result with `take!(ctx.io)`.

This function returns an expression that will deserialize the object. Several `serialize`
Expand All @@ -72,9 +72,9 @@ to do the serialization.
The deserialization code should be pretty low-level code that can be compiled
relatively easily. It especially shouldn't use global variables.

Serialization / deserialization code can use `ctx` to hold state information.
Serialization / deserialization code can use `ctx` to hold state information.

Some simple types like boxed variables do not need to write anything to `ctx.io`.
Some simple types like boxed variables do not need to write anything to `ctx.io`.
They can return an expression that directly creates the object.
"""
function serialize(ctx::SerializeContext, @nospecialize(x))
Expand Down Expand Up @@ -102,7 +102,7 @@ function serialize(ctx::SerializeContext, @nospecialize(t::DataType))
local super = $(serialize(ctx, t.super))
local parameters = $(serialize(ctx, t.parameters))
local types = $(serialize(ctx, t.types))
local ndt = ccall(:jl_new_datatype, Any,
local ndt = ccall(:jl_new_datatype, Any,
(Any, Any, Any, Any, Any, Any, Cint, Cint, Cint),
tn, tn.module, super, parameters, #=names=# unsafe_load(cglobal(:jl_any_type, Any)), types,
$(t.abstract), $(t.mutable), $(t.ninitialized))
Expand Down Expand Up @@ -196,7 +196,7 @@ function serialize(ctx::SerializeContext, a::Array{T,N}) where {T,N}
advance!(ctx.io)
ioptr = ctx.io.ptr
write(ctx.io, a)
if N == 1
if N == 1
advance!(ctx.io)
ioptr = ctx.io.ptr
write(ctx.io, a)
Expand Down
5 changes: 2 additions & 3 deletions src/utils.jl
Expand Up @@ -16,7 +16,7 @@ const jl_value_t = eltype(jl_value_t_ptr)
# const jl_svec_t_ptr = jl_value_t_ptr
# const jl_module_t_ptr = jl_value_t_ptr
# const jl_array_t_ptr = jl_value_t_ptr
#
#
# const bool_t = julia_to_llvm(Bool)
# const int8_t = julia_to_llvm(Int8)
# const int16_t = julia_to_llvm(Int16)
Expand All @@ -32,7 +32,7 @@ const jl_value_t = eltype(jl_value_t_ptr)
# const float64_t = julia_to_llvm(Float64)
# const void_t = julia_to_llvm(Nothing)
# const size_t = julia_to_llvm(Int)
#
#
# const int8_t_ptr = LLVM.PointerType(int8_t)
# const void_t_ptr = LLVM.PointerType(void_t)

Expand All @@ -54,4 +54,3 @@ walk(f, x) = true
# walk(f, x::Instruction) = foreach(c->walk(f,c), operands(x))
# walk(f, x::Instruction) = f(x) || foreach(c->walk(f,c), operands(x))
walk(f, x::ConstantExpr) = f(x) || foreach(c->walk(f,c), operands(x))

11 changes: 8 additions & 3 deletions test/ccalls.jl
Expand Up @@ -5,16 +5,20 @@ include("jlrun.jl")
# d = find_ccalls(time, Tuple{})
# d = find_ccalls(muladd, Tuple{Array{Float64,2},Array{Float64,2},Array{Float64,2}})

f1() = ccall(:jl_errno, Int, (Int,), 11)
f2() = ccall(:jl_errno, Int, (Int, Int), 21, 22)
f3() = ccall(:jl_errno, Int, (Int, Int, Int), 31, 32, 33)

@testset "ccalls" begin
f1() = ccall(:usleep, Int, (Int,), 11)
f2() = ccall(:usleep, Int, (Int, Int), 21, 22)
f3() = ccall(:usleep, Int, (Int, Int, Int), 31, 32, 33)
m1 = irgen(f1, Tuple{})
m2 = irgen(f2, Tuple{})
m3 = irgen(f3, Tuple{})
LLVM.verify(m1)
LLVM.verify(m2)
LLVM.verify(m3)
@test f1() == @jlrun f1()
@test f2() == @jlrun f2()
@test f3() == @jlrun f3()
end


Expand All @@ -25,5 +29,6 @@ end

@testset "cglobal" begin
m = irgen(f, Tuple{})
LLVM.verify(m)
@test f() == @jlrun f()
end
2 changes: 1 addition & 1 deletion test/globals.jl
Expand Up @@ -33,6 +33,6 @@ f() = Complex{Float64}
g(@nospecialize(x)) = isa(x, Number) ? 1 : 0

@testset "type" begin
@test string(@jlrun f()) == "Complex{Float64}"
@test string(@jlrun f()) == "Complex{Float64}"
res = g(4.0im)
end
51 changes: 43 additions & 8 deletions test/jlrun.jl
Expand Up @@ -2,7 +2,10 @@ using Test, StaticCompiler, Libdl
using LLVM

function show_inttoptr(mod)
for fun in LLVM.functions(mod), blk in LLVM.blocks(fun), instr in LLVM.instructions(blk)
for fun in LLVM.functions(mod),
blk in LLVM.blocks(fun),
instr in LLVM.instructions(blk)

s = string(instr)
if occursin("inttoptr", s) && occursin(r"[0-9]{8,30}", s)
println(LLVM.name(fun), " ---------------------------")
Expand All @@ -16,6 +19,18 @@ end
Compiles function call provided and calls it with `ccall` using the shared library that was created.
"""
macro jlrun(e)

# Checking gcc installation
try
if Sys.isunix()
run(`gcc -v`)
elseif Sys.iswindows()
run(`cmd /c gcc -v`)
end
catch
error("make sure gcc compiler is installed: https://gcc.gnu.org/install/binaries.html")
end

fun = e.args[1]
efun = esc(fun)
args = length(e.args) > 1 ? e.args[2:end] : Any[]
Expand All @@ -29,8 +44,24 @@ macro jlrun(e)
end
rettype = ct[1][2]
pkgdir = @__DIR__
bindir = string(Sys.BINDIR, "/../tools")
libdir = string(Sys.BINDIR, "/../lib")
bindir = joinpath(dirname(Sys.BINDIR), "tools")
libdir = joinpath(dirname(Sys.BINDIR), "lib")

# shellcmd and julia library linking
if Sys.isunix()
shellcmd = "gcc"
elseif Sys.iswindows()
shellcmd = ["cmd", "/c", "gcc"]
else
error("run command not defined")
end

runCommand = :(run(
$(`$shellcmd -shared -fPIC -o test.so -L$libdir test.o -ljulia`),
wait = true,
))


quote
m = irgen($efun, $tt)
# m = irgen($efun, $tt, overdub = false)
Expand All @@ -41,12 +72,16 @@ macro jlrun(e)
LLVM.verify(m)
# show_inttoptr(m)
write(m, "test.bc")
write_object(m, "test.o")
run($(`gcc -shared -fPIC -o test.so -L$libdir -ljulia test.o`), wait = true)
write_object(m, "test.o")
$runCommand
dylib = Libdl.dlopen($dylibpath)
ccall(Libdl.dlsym(dylib, "jl_init_globals"), Cvoid, ())
res = ccall(Libdl.dlsym(dylib, $(Meta.quot(fun))),
$rettype, ($((typeof(eval(a)) for a in args)...),), $(eval.(args)...))
ccall(Libdl.dlsym(dylib, "jl_init_globals"), Cvoid, ())
res = ccall(
Libdl.dlsym(dylib, $(Meta.quot(fun))),
$rettype,
($((typeof(eval(a)) for a in args)...),),
$(eval.(args)...),
)
Libdl.dlclose(dylib)
res
end
Expand Down
2 changes: 1 addition & 1 deletion test/ode.jl
Expand Up @@ -2,7 +2,7 @@
# MIT license
# Copyright (c) 2009-2015: various contributors: https://github.com/JuliaLang/ODE.jl/contributors

using LinearAlgebra
using LinearAlgebra

function hinit(F, x0, t0::T, tend, p, reltol, abstol) where T
# Returns first step, direction of integration and F evaluated at t0
Expand Down