Skip to content

Commit 7788852

Browse files
Cleanup (#104)
* Reorganize main compilation functions, update docstrings and README * Document and export `@device_overload` * Typo
1 parent 156210c commit 7788852

4 files changed

Lines changed: 145 additions & 100 deletions

File tree

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ using Pkg
1515
Pkg.add("StaticCompiler")
1616
```
1717

18-
There are two main ways to use this package. The first is via the `compile` function, which can be used when you want to compile a Julia function for later use from within Julia:
18+
There are two main ways to use this package:
19+
20+
### Linked compilation
21+
The first option is via the `compile` function, which can be used when you want to compile a Julia function for later use from within Julia:
1922
```julia
2023
julia> using StaticCompiler
2124

@@ -43,7 +46,8 @@ julia> fib_compiled(10)
4346
```
4447
See the file `tests/runtests.jl` for some examples of functions that work with `compile` (and some that don't, marked with `@test_skip`).
4548

46-
The second way to use this package is via the `compile_executable` and `compile_shlib` functions, for use when you want to compile a Julia function to a native executable or shared library for use from outside of Julia:
49+
### Standalone compilation
50+
The second way to use this package is via the `compile_executable` and `compile_shlib` functions, for when you want to compile a Julia function to a native executable or shared library for use from outside of Julia:
4751
```julia
4852
julia> using StaticCompiler, StaticTools
4953

@@ -68,7 +72,7 @@ This package uses the [GPUCompiler package](https://github.com/JuliaGPU/GPUCompi
6872
## Limitations
6973

7074
* GC-tracked allocations and global variables do work with `compile`, but the way they are implemented is brittle and can be dangerous. Allocate with care.
71-
* GC-tracked allocations and global variables do *not* work with `compile_executable` (yet).
72-
* Type unstable code is not yet supported.
73-
* Doesn't currently work on Windows.
74-
* If you find any other limitations, let us know. There's probably lots.
75+
* GC-tracked allocations and global variables do *not* work with `compile_executable` or `compile_shlib`. This has some interesting consequences, including that all functions _within_ the function you want to compile must either be inlined or return only native types (otherwise Julia would have to allocate a place to put the results, which will fail).
76+
* Since error handling relies on libjulia, you can only throw errors from standalone-compiled (`compile_executable` / `compile_shlib`) code if an explicit overload has been defined for that particular error with `@device_override` (see [quirks.jl](src/quirks.jl)).
77+
* Type instability. Type unstable code cannot currently be statically compiled via this package.
78+
* Doesn't work on Windows. PRs welcome.

src/StaticCompiler.jl

Lines changed: 118 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ using StaticTools: @symbolcall, @c_str, println
1313

1414
export compile, load_function, compile_shlib, compile_executable
1515
export native_code_llvm, native_code_typed, native_llvm_module, native_code_native
16+
export @device_override, @print_and_throw
1617

1718
include("target.jl")
1819
include("pointer_patching.jl")
@@ -173,7 +174,11 @@ end
173174

174175
"""
175176
```julia
176-
compile_executable(f, types::Tuple, path::String, name::String=repr(f); filename::String=name, kwargs...)
177+
compile_executable(f::Function, types::Tuple, path::String, [name::String=repr(f)];
178+
filename::String=name,
179+
cflags=``, # Specify libraries you would like to link against, and other compiler options here
180+
kwargs...
181+
)
177182
```
178183
Attempt to compile a standalone executable that runs function `f` with a type signature given by the tuple of `types`.
179184
@@ -207,7 +212,7 @@ julia> function print_args(argc::Int, argv::Ptr{Ptr{UInt8}})
207212
end
208213
209214
julia> compile_executable(print_args, (Int, Ptr{Ptr{UInt8}}))
210-
"/Users/foo/code/StaticCompiler.jl/print_args"
215+
""/Users/user/print_args""
211216
212217
shell> ./print_args 1 2 3 4 Five
213218
./print_args
@@ -223,13 +228,16 @@ julia> using StaticTools # So you don't have to define `puts` and friends every
223228
julia> hello() = println(c"Hello, world!") # c"..." makes a stack-allocated StaticString
224229
225230
julia> compile_executable(hello)
226-
"/Users/foo/code/StaticCompiler.jl/hello"
231+
"/Users/cbkeller/hello"
232+
233+
shell> ls -alh hello
234+
-rwxr-xr-x 1 user staff 33K Mar 20 21:11 hello
227235
228236
shell> ./hello
229237
Hello, world!
230238
```
231239
"""
232-
function compile_executable(f, types=(), path::String="./", name=GPUCompiler.safe_name(repr(f));
240+
function compile_executable(f::Function, types=(), path::String="./", name=GPUCompiler.safe_name(repr(f));
233241
filename=name,
234242
cflags=``,
235243
kwargs...
@@ -244,9 +252,6 @@ function compile_executable(f, types=(), path::String="./", name=GPUCompiler.saf
244252
nativetype = isprimitivetype(rt) || isa(rt, Ptr)
245253
nativetype || @warn "Return type `$rt` of `$f$types` does not appear to be a native type. Consider returning only a single value of a native machine type (i.e., a single float, int/uint, bool, or pointer). \n\nIgnoring this warning may result in Undefined Behavior!"
246254

247-
# Would be nice to use a compiler pass or something to check if there are any heap allocations or references to globals
248-
# Keep an eye on https://github.com/JuliaLang/julia/pull/43747 for this
249-
250255
generate_executable(f, tt, path, name, filename; cflags=cflags, kwargs...)
251256

252257
joinpath(abspath(path), filename)
@@ -255,11 +260,35 @@ end
255260

256261
"""
257262
```julia
258-
compile_shlib(f, types::Tuple, path::String, name::String=repr(f); filename::String=name, kwargs...)
263+
compile_shlib(f::Function, types::Tuple, [path::String="./"], [name::String=repr(f)]; filename::String=name, cflags=``, kwargs...)
264+
compile_shlib(funcs::Array, [path::String="./"]; filename="libfoo", demangle=false, cflags=``, kwargs...)
259265
```
260266
As `compile_executable`, but compiling to a standalone `.dylib`/`.so` shared library.
267+
268+
### Examples
269+
```julia
270+
julia> using StaticCompiler, LoopVectorization
271+
272+
julia> function test(n)
273+
r = 0.0
274+
@turbo for i=1:n
275+
r += log(sqrt(i))
276+
end
277+
return r/n
278+
end
279+
test (generic function with 1 method)
280+
281+
julia> compile_shlib(test, (Int,))
282+
"/Users/user/test.dylib"
283+
284+
julia> test(100_000)
285+
5.2564961094956075
286+
287+
julia> ccall(("julia_test", "test.dylib"), Float64, (Int64,), 100_000)
288+
5.2564961094956075
289+
```
261290
"""
262-
function compile_shlib(f, types=(), path::String="./", name=GPUCompiler.safe_name(repr(f));
291+
function compile_shlib(f::Function, types=(), path::String="./", name=GPUCompiler.safe_name(repr(f));
263292
filename=name,
264293
cflags=``,
265294
kwargs...
@@ -273,28 +302,34 @@ function compile_shlib(f, types=(), path::String="./", name=GPUCompiler.safe_nam
273302
nativetype = isprimitivetype(rt) || isa(rt, Ptr)
274303
nativetype || @warn "Return type `$rt` of `$f$types` does not appear to be a native type. Consider returning only a single value of a native machine type (i.e., a single float, int/uint, bool, or pointer). \n\nIgnoring this warning may result in Undefined Behavior!"
275304

276-
# Would be nice to use a compiler pass or something to check if there are any heap allocations or references to globals
277-
# Keep an eye on https://github.com/JuliaLang/julia/pull/43747 for this
278305
generate_shlib(f, tt, true, path, name, filename; cflags=cflags, kwargs...)
279306

280307
joinpath(abspath(path), filename * "." * Libdl.dlext)
281308
end
309+
# As above, but taking an array of functions and returning a single shlib
310+
function compile_shlib(funcs::Array, path::String="./";
311+
filename="libfoo",
312+
demangle=false,
313+
cflags=``,
314+
kwargs...
315+
)
316+
for func in funcs
317+
f, types = func
318+
tt = Base.to_tuple_type(types)
319+
isconcretetype(tt) || error("input type signature `$types` is not concrete")
282320

283-
function generate_shlib_fptr(f, tt, path::String=tempname(), name = GPUCompiler.safe_name(repr(f)), filename::String=name;
284-
temp::Bool=true,
285-
kwargs...)
286-
287-
generate_shlib(f, tt, false, path, name; kwargs...)
288-
lib_path = joinpath(abspath(path), "$filename.$(Libdl.dlext)")
289-
ptr = Libdl.dlopen(lib_path, Libdl.RTLD_LOCAL)
290-
fptr = Libdl.dlsym(ptr, "julia_$name")
291-
@assert fptr != C_NULL
292-
if temp
293-
atexit(()->rm(path; recursive=true))
321+
rt = last(only(native_code_typed(f, tt)))
322+
isconcretetype(rt) || error("`$f$types` did not infer to a concrete type. Got `$rt`")
323+
nativetype = isprimitivetype(rt) || isa(rt, Ptr)
324+
nativetype || @warn "Return type `$rt` of `$f$types` does not appear to be a native type. Consider returning only a single value of a native machine type (i.e., a single float, int/uint, bool, or pointer). \n\nIgnoring this warning may result in Undefined Behavior!"
294325
end
295-
fptr
326+
327+
generate_shlib(funcs, true, path, filename; demangle=demangle, cflags=cflags, kwargs...)
328+
329+
joinpath(abspath(path), filename * "." * Libdl.dlext)
296330
end
297331

332+
298333
"""
299334
```julia
300335
generate_shlib_fptr(path::String, name)
@@ -338,25 +373,40 @@ function generate_shlib_fptr(path::String, name, filename::String=name)
338373
@assert fptr != C_NULL
339374
fptr
340375
end
376+
# As above, but also compile (maybe remove this method in the future?)
377+
function generate_shlib_fptr(f, tt, path::String=tempname(), name = GPUCompiler.safe_name(repr(f)), filename::String=name;
378+
temp::Bool=true,
379+
kwargs...)
380+
381+
generate_shlib(f, tt, false, path, name; kwargs...)
382+
lib_path = joinpath(abspath(path), "$filename.$(Libdl.dlext)")
383+
ptr = Libdl.dlopen(lib_path, Libdl.RTLD_LOCAL)
384+
fptr = Libdl.dlsym(ptr, "julia_$name")
385+
@assert fptr != C_NULL
386+
if temp
387+
atexit(()->rm(path; recursive=true))
388+
end
389+
fptr
390+
end
341391

342392
"""
343393
```julia
344394
generate_executable(f, tt, path::String, name, filename=string(name); kwargs...)
345395
```
346396
Attempt to compile a standalone executable that runs `f`.
397+
Low-level interface; you should generally use `compile_executable` instead.
347398
348399
### Examples
349400
```julia
350-
julia> function test(n)
351-
r = 0.0
352-
for i=1:n
353-
r += log(sqrt(i))
354-
end
355-
return r/n
356-
end
357-
test (generic function with 1 method)
401+
julia> using StaticCompiler, StaticTools
358402
359-
julia> path, name = StaticCompiler.generate_executable(test, Tuple{Int64}, "./scratch")
403+
julia> hello() = println(c"Hello, world!")
404+
405+
julia> path, name = StaticCompiler.generate_executable(hello, Tuple{}, "./")
406+
("./", "hello")
407+
408+
shell> ./hello
409+
Hello, world!
360410
```
361411
"""
362412
function generate_executable(f, tt, path=tempname(), name=GPUCompiler.safe_name(repr(f)), filename=string(name);
@@ -404,36 +454,43 @@ end
404454

405455
"""
406456
```julia
407-
generate_shlib(f, tt, path::String, name::String, filenamebase::String="obj"; kwargs...)
457+
generate_shlib(f::Function, tt, [external::Bool=true], [path::String], [name], [filename]; kwargs...)
458+
generate_shlib(funcs::Array, [external::Bool=true], [path::String], [filename::String]; demangle=false, kwargs...)
408459
```
409460
Low level interface for compiling a shared object / dynamically loaded library
410461
(`.so` / `.dylib`) for function `f` given a tuple type `tt` characterizing
411462
the types of the arguments for which the function will be compiled.
412-
See also `StaticCompiler.generate_shlib_fptr`.
463+
413464
### Examples
414465
```julia
466+
julia> using StaticCompiler, LoopVectorization
467+
415468
julia> function test(n)
416469
r = 0.0
417-
for i=1:n
470+
@turbo for i=1:n
418471
r += log(sqrt(i))
419472
end
420473
return r/n
421474
end
422475
test (generic function with 1 method)
423-
julia> path, name = StaticCompiler.generate_shlib(test, Tuple{Int64}, "./test")
424-
("./test", "test")
476+
477+
julia> path, name = StaticCompiler.generate_shlib(test, Tuple{Int64}, true, "./example")
478+
("./example", "test")
479+
425480
shell> tree \$path
426-
./test
427-
|-- obj.o
428-
`-- obj.so
481+
./example
482+
|-- test.dylib
483+
`-- test.o
429484
0 directories, 2 files
485+
430486
julia> test(100_000)
431-
5.256496109495593
432-
julia> ccall(StaticCompiler.generate_shlib_fptr(path, name), Float64, (Int64,), 100_000)
433-
5.256496109495593
487+
5.2564961094956075
488+
489+
julia> ccall(("julia_test", "example/test.dylib"), Float64, (Int64,), 100_000)
490+
5.2564961094956075
434491
```
435492
"""
436-
function generate_shlib(f, tt, external = true, path=tempname(), name=GPUCompiler.safe_name(repr(f)), filename=name;
493+
function generate_shlib(f::Function, tt, external::Bool=true, path::String=tempname(), name=GPUCompiler.safe_name(repr(f)), filename=name;
437494
cflags=``,
438495
kwargs...
439496
)
@@ -455,6 +512,23 @@ function generate_shlib(f, tt, external = true, path=tempname(), name=GPUCompile
455512

456513
path, name
457514
end
515+
# As above, but taking an array of functions and returning a single shlib
516+
function generate_shlib(funcs::Array, external::Bool=true, path::String=tempname(), filename::String="libfoo";
517+
demangle=false,
518+
cflags=``,
519+
kwargs...
520+
)
521+
522+
lib_path = joinpath(path, "$filename.$(Libdl.dlext)")
523+
524+
_,obj_path = generate_obj(funcs, external, path, filename; demangle=demangle, kwargs...)
525+
# Pick a Clang
526+
cc = Sys.isapple() ? `cc` : clang()
527+
# Compile!
528+
run(`$cc -shared $cflags $obj_path -o $lib_path `)
529+
530+
path, name
531+
end
458532

459533
function native_code_llvm(@nospecialize(func), @nospecialize(types); kwargs...)
460534
job, kwargs = native_job(func, types, true; kwargs...)
@@ -524,46 +598,4 @@ function generate_obj(funcs::Array, external, path::String = tempname(), filenam
524598
path, obj_path
525599
end
526600

527-
function generate_shlib(funcs::Array, external = true, path::String = tempname(), filename::String="libfoo";
528-
demangle=false,
529-
cflags=``,
530-
kwargs...
531-
)
532-
533-
lib_path = joinpath(path, "$filename.$(Libdl.dlext)")
534-
535-
_,obj_path = generate_obj(funcs, external, path, filename; demangle=demangle, kwargs...)
536-
# Pick a Clang
537-
cc = Sys.isapple() ? `cc` : clang()
538-
# Compile!
539-
run(`$cc -shared $cflags $obj_path -o $lib_path `)
540-
541-
path, name
542-
end
543-
544-
function compile_shlib(funcs::Array, path::String="./";
545-
filename="libfoo",
546-
demangle=false,
547-
cflags=``,
548-
kwargs...)
549-
for func in funcs
550-
f, types = func
551-
tt = Base.to_tuple_type(types)
552-
isconcretetype(tt) || error("input type signature `$types` is not concrete")
553-
554-
rt = last(only(native_code_typed(f, tt)))
555-
isconcretetype(rt) || error("`$f$types` did not infer to a concrete type. Got `$rt`")
556-
nativetype = isprimitivetype(rt) || isa(rt, Ptr)
557-
nativetype || @warn "Return type `$rt` of `$f$types` does not appear to be a native type. Consider returning only a single value of a native machine type (i.e., a single float, int/uint, bool, or pointer). \n\nIgnoring this warning may result in Undefined Behavior!"
558-
end
559-
560-
# Would be nice to use a compiler pass or something to check if there are any heap allocations or references to globals
561-
# Keep an eye on https://github.com/JuliaLang/julia/pull/43747 for this
562-
563-
generate_shlib(funcs, true, path, filename; demangle=demangle, cflags=cflags, kwargs...)
564-
565-
joinpath(abspath(path), filename * "." * Libdl.dlext)
566-
end
567-
568-
569601
end # module

src/target.jl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,18 @@ end
66

77
const overrides = quote end
88

9-
9+
"""
10+
```julia
11+
@device_override old_bad_method(arg1::Type1, arg2::Type2) = new_good_method(arg1, arg2)
12+
```
13+
Override a non-static-compilable method (e.g. `old_bad_method(::Type1, ::Type2)`)
14+
with a more compileable replacement.
15+
### Examples
16+
```
17+
@device_override @noinline Core.throw_inexacterror(f::Symbol, ::Type{T}, val) where {T} =
18+
@print_and_throw c"Inexact conversion"
19+
```
20+
"""
1021
macro device_override(ex)
1122
ex = macroexpand(__module__, ex)
1223
if Meta.isexpr(ex, :call)
@@ -24,7 +35,6 @@ macro device_override(ex)
2435
end
2536
end
2637

27-
2838
Base.@kwdef struct NativeCompilerTarget <: GPUCompiler.AbstractCompilerTarget
2939
cpu::String=(LLVM.version() < v"8") ? "" : unsafe_string(LLVM.API.LLVMGetHostCPUName())
3040
features::String=(LLVM.version() < v"8") ? "" : unsafe_string(LLVM.API.LLVMGetHostCPUFeatures())

0 commit comments

Comments
 (0)