Skip to content

Commit

Permalink
add --trim option for generating smaller binaries (#55047)
Browse files Browse the repository at this point in the history
This adds a command line option `--trim` that builds images where code
is only included if it is statically reachable from methods marked using
the new function `entrypoint`. Compile-time errors are given for call
sites that are too dynamic to allow trimming the call graph (however
there is an `unsafe` option if you want to try building anyway to see
what happens).

The PR has two other components. One is changes to Base that generally
allow more code to be compiled in this mode. These changes will either
be merged in separate PRs or moved to a separate part of the workflow
(where we will build a custom system image for this purpose). The branch
is set up this way to make it easy to check out and try the
functionality.

The other component is everything in the `juliac/` directory, which
implements a compiler driver script based on this new option, along with
some examples and tests. This will eventually become a package "app"
that depends on PackageCompiler and provides a CLI for all of this
stuff, so it will not be merged here. To try an example:

```
julia contrib/juliac.jl --output-exe hello --trim test/trimming/hello.jl
```

When stripped the resulting executable is currently about 900kb on my
machine.

Also includes a lot of work by @topolarity

---------

Co-authored-by: Gabriel Baraldi <[email protected]>
Co-authored-by: Tim Holy <[email protected]>
Co-authored-by: Cody Tapscott <[email protected]>
  • Loading branch information
4 people authored Sep 28, 2024
1 parent ff0a1be commit 97ecdb8
Show file tree
Hide file tree
Showing 37 changed files with 1,338 additions and 89 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ julia-deps: | $(DIRS) $(build_datarootdir)/julia/base $(build_datarootdir)/julia
julia-stdlib: | $(DIRS) julia-deps
@$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/stdlib

julia-base: julia-deps $(build_sysconfdir)/julia/startup.jl $(build_man1dir)/julia.1 $(build_datarootdir)/julia/julia-config.jl
julia-base: julia-deps $(build_sysconfdir)/julia/startup.jl $(build_man1dir)/julia.1 $(build_datarootdir)/julia/julia-config.jl $(build_datarootdir)/julia/juliac.jl $(build_datarootdir)/julia/juliac-buildscript.jl
@$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/base

julia-libccalltest: julia-deps
Expand Down Expand Up @@ -181,7 +181,7 @@ $(build_sysconfdir)/julia/startup.jl: $(JULIAHOME)/etc/startup.jl | $(build_sysc
@echo Creating usr/etc/julia/startup.jl
@cp $< $@

$(build_datarootdir)/julia/julia-config.jl: $(JULIAHOME)/contrib/julia-config.jl | $(build_datarootdir)/julia
$(build_datarootdir)/julia/%: $(JULIAHOME)/contrib/% | $(build_datarootdir)/julia
$(INSTALL_M) $< $(dir $@)

$(build_depsbindir)/stringreplace: $(JULIAHOME)/contrib/stringreplace.c | $(build_depsbindir)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Julia v1.12 Release Notes
New language features
---------------------

- New option `--trim` for building "trimmed" binaries, where code not provably reachable from entry points
is removed. Entry points can be marked using `Base.Experimental.entrypoint` ([#55047]).
- A new keyword argument `usings::Bool` has been added to `names`. By using this, we can now
find all the names available in module `A` by `names(A; all=true, imported=true, usings=true)`. ([#54609])
- the `@atomic(...)` macro family supports now the reference assignment syntax, e.g.
Expand Down
14 changes: 14 additions & 0 deletions base/experimental.jl
Original file line number Diff line number Diff line change
Expand Up @@ -457,4 +457,18 @@ without adding them to the global method table.
"""
:@MethodTable

"""
Base.Experimental.entrypoint(f, argtypes::Tuple)
Mark a method for inclusion when the `--trim` option is specified.
"""
function entrypoint(@nospecialize(f), @nospecialize(argtypes::Tuple))
entrypoint(Tuple{Core.Typeof(f), argtypes...})
end

function entrypoint(@nospecialize(argt::Type))
ccall(:jl_add_entrypoint, Int32, (Any,), argt)
nothing
end

end
5 changes: 4 additions & 1 deletion base/libuv.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ function uv_return_spawn end
function uv_asynccb end
function uv_timercb end

function reinit_stdio()
reinit_stdio() = _reinit_stdio()
# we need this so it can be called by codegen to print errors, even after
# reinit_stdio has been redefined by the juliac build script.
function _reinit_stdio()
global stdin = init_stdio(ccall(:jl_stdin_stream, Ptr{Cvoid}, ()))::IO
global stdout = init_stdio(ccall(:jl_stdout_stream, Ptr{Cvoid}, ()))::IO
global stderr = init_stdio(ccall(:jl_stderr_stream, Ptr{Cvoid}, ()))::IO
Expand Down
1 change: 1 addition & 0 deletions base/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ struct JLOptions
permalloc_pkgimg::Int8
heap_size_hint::UInt64
trace_compile_timing::Int8
trim::Int8
end

# This runs early in the sysimage != is not defined yet
Expand Down
11 changes: 9 additions & 2 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,13 @@ struct CodegenParams
"""
use_jlplt::Cint

"""
If enabled, only provably reachable code (from functions marked with `entrypoint`) is included
in the output system image. Errors or warnings can be given for call sites too dynamic to handle.
The option is disabled by default. (0=>disabled, 1=>safe (static errors), 2=>unsafe, 3=>unsafe plus warnings)
"""
trim::Cint

"""
A pointer of type
Expand All @@ -1519,14 +1526,14 @@ struct CodegenParams
prefer_specsig::Bool=false,
gnu_pubnames::Bool=true, debug_info_kind::Cint = default_debug_info_kind(),
debug_info_level::Cint = Cint(JLOptions().debug_level), safepoint_on_entry::Bool=true,
gcstack_arg::Bool=true, use_jlplt::Bool=true,
gcstack_arg::Bool=true, use_jlplt::Bool=true, trim::Cint=Cint(0),
lookup::Ptr{Cvoid}=unsafe_load(cglobal(:jl_rettype_inferred_addr, Ptr{Cvoid})))
return new(
Cint(track_allocations), Cint(code_coverage),
Cint(prefer_specsig),
Cint(gnu_pubnames), debug_info_kind,
debug_info_level, Cint(safepoint_on_entry),
Cint(gcstack_arg), Cint(use_jlplt),
Cint(gcstack_arg), Cint(use_jlplt), Cint(trim),
lookup)
end
end
Expand Down
4 changes: 4 additions & 0 deletions base/strings/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ function print(io::IO, xs...)
return nothing
end

setfield!(typeof(print).name.mt, :max_args, 10, :monotonic)

"""
println([io::IO], xs...)
Expand All @@ -74,6 +76,7 @@ julia> String(take!(io))
"""
println(io::IO, xs...) = print(io, xs..., "\n")

setfield!(typeof(println).name.mt, :max_args, 10, :monotonic)
## conversion of general objects to strings ##

"""
Expand Down Expand Up @@ -149,6 +152,7 @@ function print_to_string(xs...)
end
String(_unsafe_take!(s))
end
setfield!(typeof(print_to_string).name.mt, :max_args, 10, :monotonic)

function string_with_env(env, xs...)
if isempty(xs)
Expand Down
2 changes: 1 addition & 1 deletion contrib/julia-config.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function ldlibs(doframework)
"julia"
end
if Sys.isunix()
return "-Wl,-rpath,$(shell_escape(libDir())) -Wl,-rpath,$(shell_escape(private_libDir())) -l$libname"
return "-L$(shell_escape(private_libDir())) -Wl,-rpath,$(shell_escape(libDir())) -Wl,-rpath,$(shell_escape(private_libDir())) -l$libname"
else
return "-l$libname -lopenlibm"
end
Expand Down
Loading

0 comments on commit 97ecdb8

Please sign in to comment.