@@ -7,20 +7,20 @@ using Base: RefValue
77using Serialization: serialize, deserialize
88using Clang_jll: clang
99
10- export compile, load_function
10+ export compile, load_function, compile_executable
1111export native_code_llvm, native_code_typed, native_llvm_module
1212
1313"""
1414 compile(f, types, path::String = tempname()) --> (compiled_f, path)
1515
16- !!! Warning: this will fail on programs that heap allocate any memory, or have dynamic dispatch !!!
16+ !!! Warning: this will fail on programs that heap allocate any memory tracked by the GC , or have dynamic dispatch !!!
1717
1818Statically compile the method of a function `f` specialized to arguments of the type given by `types`.
1919
2020This will create a directory at the specified path (or in a temporary directory if you exclude that argument)
2121that contains the files needed for your static compiled function. `compile` will return a
22- `StaticCompiledFunction` object and `obj_path` which is the absolute path of the directory containing the
23- compilation artifacts. The `StaticCompiledFunction` can be treated as if it is a function with a single
22+ `StaticCompiledFunction` object and `obj_path` which is the absolute path of the directory containing the
23+ compilation artifacts. The `StaticCompiledFunction` can be treated as if it is a function with a single
2424method corresponding to the types you specified when it was compiled.
2525
2626To deserialize and instantiate a previously compiled function, simply execute `load_function(path)`, which
6868
69690 directories, 3 files
7070````
71- * `obj.so` (or `.dylib` on MacOS) is a shared object file that can be linked to in order to execute your
72- compiled julia function.
71+ * `obj.so` (or `.dylib` on MacOS) is a shared object file that can be linked to in order to execute your
72+ compiled julia function.
7373* `obj.cjl` is a serialized `LazyStaticCompiledFunction` object which will be deserialized and instantiated
74- with `load_function(path)`. `LazyStaticcompiledfunction`s contain the requisite information needed to link to the
75- `obj.so` inside a julia session. Once it is instantiated in a julia session (i.e. by
76- `instantiate(::LazyStaticCompiledFunction)`, this happens automatically in `load_function`), it will be of type
77- `StaticCompiledFunction` and may be called with arguments of type `types` as if it were a function with a
78- single method (the method determined by `types`).
74+ with `load_function(path)`. `LazyStaticcompiledfunction`s contain the requisite information needed to link to the
75+ `obj.so` inside a julia session. Once it is instantiated in a julia session (i.e. by
76+ `instantiate(::LazyStaticCompiledFunction)`, this happens automatically in `load_function`), it will be of type
77+ `StaticCompiledFunction` and may be called with arguments of type `types` as if it were a function with a
78+ single method (the method determined by `types`).
7979"""
8080function compile (f, _tt, path:: String = tempname (); name = GPUCompiler. safe_name (repr (f)), kwargs... )
8181 tt = Base. to_tuple_type (_tt)
134134
135135instantiate (f:: StaticCompiledFunction ) = f
136136
137+
138+ """
139+ ```julia
140+ compile_executable(f, types::Tuple, path::String, name::String=repr(f); filename::String=name, kwargs...)
141+ ```
142+ Attempt to compile a standalone executable that runs function `f` with a type signature given by the tuple of `types`.
143+
144+ ### Examples
145+ ```julia
146+ julia> using StaticCompiler
147+
148+ julia> function puts(s::Ptr{UInt8}) # Can't use Base.println because it allocates.
149+ # Note, this `llvmcall` requires Julia 1.8+
150+ Base.llvmcall((\" ""
151+ ; External declaration of the puts function
152+ declare i32 @puts(i8* nocapture) nounwind
153+
154+ define i32 @main(i8*) {
155+ entry:
156+ %call = call i32 (i8*) @puts(i8* %0)
157+ ret i32 0
158+ }
159+ \" "", "main"), Int32, Tuple{Ptr{UInt8}}, s)
160+ end
161+ puts (generic function with 1 method)
162+
163+ julia> function print_args(argc::Int, argv::Ptr{Ptr{UInt8}})
164+ for i=1:argc
165+ # Get pointer
166+ p = unsafe_load(argv, i)
167+ # Print string at pointer location (which fortunately already exists isn't tracked by the GC)
168+ puts(p)
169+ end
170+ return 0
171+ end
172+
173+ julia> compile_executable(print_args, (Int, Ptr{Ptr{UInt8}}))
174+ "/Users/foo/code/StaticCompiler.jl/print_args"
175+
176+ shell> ./print_args 1 2 3 4 Five
177+ ./print_args
178+ 1
179+ 2
180+ 3
181+ 4
182+ Five
183+ ```
184+ ```julia
185+ julia> using StaticTools # So you don't have to define `puts` and friends every time
186+
187+ julia> hello() = println(c"Hello, world!") # c"..." makes a stack-allocated StaticString
188+
189+ julia> compile_executable(hello)
190+ "/Users/foo/code/StaticCompiler.jl/hello"
191+
192+ shell> ./hello
193+ Hello, world!
194+ ```
195+ """
196+ function compile_executable (f, _tt= (), path:: String = " ./" , name= GPUCompiler. safe_name (repr (f)); filename= name, kwargs... )
197+ tt = Base. to_tuple_type (_tt)
198+ tt == Tuple{} || tt == Tuple{Int, Ptr{Ptr{UInt8}}} || error (" input type signature $_tt must be either () or (Int, Ptr{Ptr{UInt8}})" )
199+
200+ rt = only (native_code_typed (f, tt))[2 ]
201+ isconcretetype (rt) || error (" $f$_tt did not infer to a concrete type. Got $rt " )
202+
203+ # Would be nice to use a compiler pass or something to check if there are any heap allocations or references to globals
204+ # Keep an eye on https://github.com/JuliaLang/julia/pull/43747 for this
205+
206+ generate_executable (f, tt, path, name, filename; kwargs... )
207+
208+ joinpath (abspath (path), filename)
209+ end
210+
211+
137212module TestRuntime
138213 # dummy methods
139214 signal_exception () = return
160235
161236"""
162237```julia
163- generate_shlib(f, tt, path::String, name::String; kwargs...)
238+ generate_shlib(f, tt, path::String, name::String, filenamebase::String="obj" ; kwargs...)
164239```
165240Low level interface for compiling a shared object / dynamically loaded library
166241 (`.so` / `.dylib`) for function `f` given a tuple type `tt` characterizing
@@ -196,10 +271,10 @@ julia> ccall(StaticCompiler.generate_shlib_fptr(path, name), Float64, (Int64,),
1962715.256496109495593
197272```
198273"""
199- function generate_shlib (f, tt, path:: String = tempname (), name = GPUCompiler. safe_name (repr (f)); kwargs... )
274+ function generate_shlib (f, tt, path:: String = tempname (), name = GPUCompiler. safe_name (repr (f)), filenamebase :: String = " obj " ; kwargs... )
200275 mkpath (path)
201- obj_path = joinpath (path, " obj .o" )
202- lib_path = joinpath (path, " obj .$(Libdl. dlext) " )
276+ obj_path = joinpath (path, " $filenamebase .o" )
277+ lib_path = joinpath (path, " $filenamebase .$(Libdl. dlext) " )
203278 open (obj_path, " w" ) do io
204279 job, kwargs = native_job (f, tt; name, kwargs... )
205280 obj, _ = GPUCompiler. codegen (:obj , job; strip= true , only_entry= false , validate= false )
@@ -216,9 +291,9 @@ function generate_shlib(f, tt, path::String = tempname(), name = GPUCompiler.saf
216291end
217292
218293
219- function generate_shlib_fptr (f, tt, path:: String = tempname (), name = GPUCompiler. safe_name (repr (f)); temp:: Bool = true , kwargs... )
294+ function generate_shlib_fptr (f, tt, path:: String = tempname (), name = GPUCompiler. safe_name (repr (f)), filenamebase :: String = " obj " ; temp:: Bool = true , kwargs... )
220295 generate_shlib (f, tt, path, name; kwargs... )
221- lib_path = joinpath (abspath (path), " obj .$(Libdl. dlext) " )
296+ lib_path = joinpath (abspath (path), " $filenamebase .$(Libdl. dlext) " )
222297 ptr = Libdl. dlopen (lib_path, Libdl. RTLD_LOCAL)
223298 fptr = Libdl. dlsym (ptr, " julia_$name " )
224299 @assert fptr != C_NULL
@@ -236,7 +311,7 @@ Low level interface for obtaining a function pointer by `dlopen`ing a shared
236311library given the `path` and `name` of a `.so`/`.dylib` already compiled by
237312`generate_shlib`.
238313
239- See also `StaticCompiler.enerate_shlib `.
314+ See also `StaticCompiler.generate_shlib `.
240315
241316### Examples
242317```julia
@@ -264,14 +339,70 @@ julia> test(100_000)
2643395.256496109495593
265340```
266341"""
267- function generate_shlib_fptr (path:: String , name)
268- lib_path = joinpath (abspath (path), " obj .$(Libdl. dlext) " )
342+ function generate_shlib_fptr (path:: String , name, filenamebase :: String = " obj " )
343+ lib_path = joinpath (abspath (path), " $filenamebase .$(Libdl. dlext) " )
269344 ptr = Libdl. dlopen (lib_path, Libdl. RTLD_LOCAL)
270345 fptr = Libdl. dlsym (ptr, " julia_$name " )
271346 @assert fptr != C_NULL
272347 fptr
273348end
274349
350+ """
351+ ```julia
352+ generate_executable(f, tt, path::String, name, filename=string(name); kwargs...)
353+ ```
354+ Attempt to compile a standalone executable that runs `f`.
355+
356+ ### Examples
357+ ```julia
358+ julia> function test(n)
359+ r = 0.0
360+ for i=1:n
361+ r += log(sqrt(i))
362+ end
363+ return r/n
364+ end
365+ test (generic function with 1 method)
366+
367+ julia> path, name = StaticCompiler.generate_executable(test, Tuple{Int64}, "./scratch")
368+ ```
369+ """
370+ function generate_executable (f, tt, path:: String = tempname (), name = GPUCompiler. safe_name (repr (f)), filename:: String = string (name); kwargs... )
371+ mkpath (path)
372+ obj_path = joinpath (path, " $filename .o" )
373+ exec_path = joinpath (path, filename)
374+ open (obj_path, " w" ) do io
375+ job, kwargs = native_job (f, tt; name, kwargs... )
376+ obj, _ = GPUCompiler. codegen (:obj , job; strip= true , only_entry= false , validate= false )
377+
378+ write (io, obj)
379+ flush (io)
380+
381+ # Pick a compiler
382+ cc = Sys. isapple () ? ` cc` : clang ()
383+ # Compile!
384+ if Sys. isapple ()
385+ # Apple no longer uses _start, so we can just specify a custom entry
386+ entry = " _julia_$name "
387+ run (` $cc -e $entry $obj_path -o $exec_path ` )
388+ else
389+ # Write a minimal wrapper to avoid having to specify a custom entry
390+ wrapper_path = joinpath (path, " wrapper.c" )
391+ f = open (wrapper_path, " w" )
392+ print (f, """ int main(int argc, char** argv)
393+ {
394+ julia_$name (argc, argv);
395+ return 0;
396+ }""" )
397+ close (f)
398+ run (` $cc $wrapper_path $obj_path -o $exec_path ` )
399+ # Clean up
400+ run (` rm $wrapper_path ` )
401+ end
402+ end
403+ path, name
404+ end
405+
275406function native_code_llvm (@nospecialize (func), @nospecialize (types); kwargs... )
276407 job, kwargs = native_job (func, types; kwargs... )
277408 GPUCompiler. code_llvm (stdout , job; kwargs... )
0 commit comments