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

add --trim option for generating smaller binaries #55047

Merged
merged 37 commits into from
Sep 28, 2024

Conversation

JeffBezanson
Copy link
Sponsor Member

@JeffBezanson JeffBezanson commented Jul 5, 2024

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

@JeffBezanson JeffBezanson added the status:DO NOT MERGE Do not merge this PR! label Jul 5, 2024
@ViralBShah ViralBShah marked this pull request as draft July 5, 2024 23:14
@ViralBShah
Copy link
Member

ViralBShah commented Jul 6, 2024

➜  juliac git:(jb/gb/static-call-graph) ✗ ../julia juliac.jl --output-exe hello --trim exe_examples/hello_world.jl
ld: warning: ignoring duplicate libraries: '-ljulia'
ld: warning: reexported library with install name '@rpath/libunwind.1.dylib' found at '/Users/viral/julia/usr/lib/libunwind.1.0.dylib' couldn't be matched with any parent library and will be linked directly
run(`cc $(allflags) -o $outname -Wl,$(Base.Linking.WHOLE_ARCHIVE) $img_path -Wl,$(Base.Linking.NO_WHOLE_ARCHIVE) $init_path $(julia_libs)`) = Process(`cc -std=gnu11 -I/Users/viral/julia/usr/include/julia -fPIC -L/Users/viral/julia/usr/lib -L/Users/viral/julia/usr/lib/julia -Wl,-rpath,/Users/viral/julia/usr/lib -ljulia -o hello -Wl,-all_load /var/folders/qj/54cx7lcs2qv9z1b4f_110xb00000gn/T/jl_fPbFYU/img.a -Wl, /var/folders/qj/54cx7lcs2qv9z1b4f_110xb00000gn/T/jl_fPbFYU/init.a -ljulia -ljulia-internal`, ProcessExited(0))

1M binary on Mac M-series:

➜  juliac git:(jb/gb/static-call-graph) ✗ ls -l hello
-rwxr-xr-x  1 viral  staff  1082296 Jul  6 09:57 hello
➜  juliac git:(jb/gb/static-call-graph) ✗ ./hello
Hello, world!
➜  juliac git:(jb/gb/static-call-graph) ✗ otool -L hello
hello:
	@rpath/libjulia.1.12.dylib (compatibility version 1.12.0, current version 1.12.0)
	@rpath/libjulia-internal.dylib (compatibility version 1.12.0, current version 1.12.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.100.2)

topolarity added a commit that referenced this pull request Jul 13, 2024
TypedCallable provides a wrapper for callable objects, with the following benefits:
    1. Enforced type-stability (for concrete AT/RT types)
    2. Fast calling convention (frequently < 10 ns / call)
    3. Normal Julia dispatch semantics (sees new Methods, etc.) + invoke_latest
    4. Pre-compilation support (including `--trim` compatibility)

It can be used like this:
```julia
callbacks = @TypedCallable{(::Int,::Int)->Bool}[]

register_callback!(callbacks, f::F) where {F<:Function} =
    push!(callbacks, @TypedCallable f(::Int,::Int)::Bool)

register_callback!(callbacks, (x,y)->(x == y))
register_callback!(callbacks, (x,y)->(x != y))

@Btime callbacks[rand(1:2)](1,1)
```

This is very similar to the existing `FunctionWrappers.jl`, but there
are a few key differences:
  - Better type support: TypedCallable supports the full range of Julia
    types (incl. Varargs), and it has access to all of Julia's "internal"
    calling conventions so calls are fast (and allocation-free) for a
    wider range of input types
  - Improved dispatch handling: The `@cfunction` functionality used by
    FunctionWrappers has several dispatch bugs, which cause wrappers to
    occasionally not see new Methods. These bugs are fixed (or soon to
    be fixed) for TypedCallable.
  - Pre-compilation support including for `juliac` / `--trim` (#55047)

Many of the improvements here are actually thanks to the `OpaqueClosure`
introduced by @Keno - This type just builds on top of OpaqueClosure to
provide an interface with Julia's usual dispatch semantics.
topolarity added a commit that referenced this pull request Jul 13, 2024
TypedCallable provides a wrapper for callable objects, with the following benefits:
    1. Enforced type-stability (for concrete AT/RT types)
    2. Fast calling convention (frequently < 10 ns / call)
    3. Normal Julia dispatch semantics (sees new Methods, etc.) + invoke_latest
    4. Pre-compilation support (including `--trim` compatibility)

It can be used like this:
```julia
callbacks = @TypedCallable{(::Int,::Int)->Bool}[]

register_callback!(callbacks, f::F) where {F<:Function} =
    push!(callbacks, @TypedCallable f(::Int,::Int)::Bool)

register_callback!(callbacks, (x,y)->(x == y))
register_callback!(callbacks, (x,y)->(x != y))

@Btime callbacks[rand(1:2)](1,1)
```

This is very similar to the existing `FunctionWrappers.jl`, but there
are a few key differences:
  - Better type support: TypedCallable supports the full range of Julia
    types (incl. Varargs), and it has access to all of Julia's "internal"
    calling conventions so calls are fast (and allocation-free) for a
    wider range of input types
  - Improved dispatch handling: The `@cfunction` functionality used by
    FunctionWrappers has several dispatch bugs, which cause wrappers to
    occasionally not see new Methods. These bugs are fixed (or soon to
    be fixed) for TypedCallable.
  - Pre-compilation support including for `juliac` / `--trim` (#55047)

Many of the improvements here are actually thanks to the `OpaqueClosure`
introduced by @Keno - This type just builds on top of OpaqueClosure to
provide an interface with Julia's usual dispatch semantics.
topolarity added a commit that referenced this pull request Jul 13, 2024
TypedCallable provides a wrapper for callable objects, with the following benefits:
    1. Enforced type-stability (for concrete AT/RT types)
    2. Fast calling convention (frequently < 10 ns / call)
    3. Normal Julia dispatch semantics (sees new Methods, etc.) + invoke_latest
    4. Pre-compilation support (including `--trim` compatibility)

It can be used like this:
```julia
callbacks = @TypedCallable{(::Int,::Int)->Bool}[]

register_callback!(callbacks, f::F) where {F<:Function} =
    push!(callbacks, @TypedCallable f(::Int,::Int)::Bool)

register_callback!(callbacks, (x,y)->(x == y))
register_callback!(callbacks, (x,y)->(x != y))

@Btime callbacks[rand(1:2)](1,1)
```

This is very similar to the existing `FunctionWrappers.jl`, but there
are a few key differences:
  - Better type support: TypedCallable supports the full range of Julia
    types (incl. Varargs), and it has access to all of Julia's "internal"
    calling conventions so calls are fast (and allocation-free) for a
    wider range of input types
  - Improved dispatch handling: The `@cfunction` functionality used by
    FunctionWrappers has several dispatch bugs, which cause wrappers to
    occasionally not see new Methods. These bugs are fixed (or soon to
    be fixed) for TypedCallable.
  - Pre-compilation support including for `juliac` / `--trim` (#55047)

Many of the improvements here are actually thanks to the `OpaqueClosure`
introduced by @Keno - This type just builds on top of OpaqueClosure to
provide an interface with Julia's usual dispatch semantics.
@topolarity topolarity mentioned this pull request Jul 13, 2024
3 tasks
topolarity added a commit that referenced this pull request Jul 13, 2024
TypedCallable provides a wrapper for callable objects, with the following benefits:
    1. Enforced type-stability (for concrete AT/RT types)
    2. Fast calling convention (frequently < 10 ns / call)
    3. Normal Julia dispatch semantics (sees new Methods, etc.) + invoke_latest
    4. Pre-compilation support (including `--trim` compatibility)

It can be used like this:
```julia
callbacks = @TypedCallable{(::Int,::Int)->Bool}[]

register_callback!(callbacks, f::F) where {F<:Function} =
    push!(callbacks, @TypedCallable f(::Int,::Int)::Bool)

register_callback!(callbacks, (x,y)->(x == y))
register_callback!(callbacks, (x,y)->(x != y))

@Btime callbacks[rand(1:2)](1,1)
```

This is very similar to the existing `FunctionWrappers.jl`, but there
are a few key differences:
  - Better type support: TypedCallable supports the full range of Julia
    types (incl. Varargs), and it has access to all of Julia's "internal"
    calling conventions so calls are fast (and allocation-free) for a
    wider range of input types
  - Improved dispatch handling: The `@cfunction` functionality used by
    FunctionWrappers has several dispatch bugs, which cause wrappers to
    occasionally not see new Methods. These bugs are fixed (or soon to
    be fixed) for TypedCallable.
  - Pre-compilation support including for `juliac` / `--trim` (#55047)

Many of the improvements here are actually thanks to the `OpaqueClosure`
introduced by @Keno - This type just builds on top of OpaqueClosure to
provide an interface with Julia's usual dispatch semantics.
topolarity added a commit that referenced this pull request Jul 13, 2024
TypedCallable provides a wrapper for callable objects, with the following benefits:
    1. Enforced type-stability (for concrete AT/RT types)
    2. Fast calling convention (frequently < 10 ns / call)
    3. Normal Julia dispatch semantics (sees new Methods, etc.) + invoke_latest
    4. Pre-compilation support (including `--trim` compatibility)

It can be used like this:
```julia
callbacks = @TypedCallable{(::Int,::Int)->Bool}[]

register_callback!(callbacks, f::F) where {F<:Function} =
    push!(callbacks, @TypedCallable f(::Int,::Int)::Bool)

register_callback!(callbacks, (x,y)->(x == y))
register_callback!(callbacks, (x,y)->(x != y))

@Btime callbacks[rand(1:2)](1,1)
```

This is very similar to the existing `FunctionWrappers.jl`, but there
are a few key differences:
  - Better type support: TypedCallable supports the full range of Julia
    types (incl. Varargs), and it has access to all of Julia's "internal"
    calling conventions so calls are fast (and allocation-free) for a
    wider range of input types
  - Improved dispatch handling: The `@cfunction` functionality used by
    FunctionWrappers has several dispatch bugs, which cause wrappers to
    occasionally not see new Methods. These bugs are fixed (or soon to
    be fixed) for TypedCallable.
  - Pre-compilation support including for `juliac` / `--trim` (#55047)

Many of the improvements here are actually thanks to the `OpaqueClosure`
introduced by @Keno - This type just builds on top of OpaqueClosure to
provide an interface with Julia's usual dispatch semantics.

Co-authored-by: Gabriel Baraldi <[email protected]>
@timholy
Copy link
Sponsor Member

timholy commented Jul 15, 2024

#55104 gets the lib example working

@MasonProtter
Copy link
Contributor

MasonProtter commented Jul 15, 2024

Let me know if issues should be raised elsewhere, but I ran into a problem where the compiled binaries don't seem to get ARGS. Here's a MWE:

# args.jl
module ArgsDemo

Base.@ccallable function main()::Cint
    ccall(:jl_, Cvoid, (Any,), ARGS)
    return 0
end

end
❯ ~/julia/./julia --startup=no ~/julia/juliac/juliac.jl --output-exe args --trim args.jl
run(`cc $(allflags) -o $outname -Wl,$(Base.Linking.WHOLE_ARCHIVE) $img_path -Wl,$(Base.Linking.NO_WHOLE_ARCHIVE) $init_path $(julia_libs)`) = Process(`cc -std=gnu11 -I/home/masonp/julia/usr/include/julia -fPIC -L/home/masonp/julia/usr/lib -Wl,--export-dynamic -L/home/masonp/julia/usr/lib/julia -Wl,-rpath,/home/masonp/julia/usr/lib -Wl,-rpath,/home/masonp/julia/usr/lib/julia -ljulia -o args -Wl,--whole-archive /tmp/jl_2vsSFl/img.a -Wl,--no-whole-archive /tmp/jl_2vsSFl/init.a -ljulia -ljulia-internal`, ProcessExited(0))

❯ ./args
Array{String, 1}(dims=(0,), mem=Memory{String}(8, 0x5589c120d310)[#<null>, #<null>, #<null>, #<null>, #<null>, #<null>, #<null>, #<null>])

❯ ./args arg1 arg2 agr3
Array{String, 1}(dims=(0,), mem=Memory{String}(8, 0x558aac9f6310)[#<null>, #<null>, #<null>, #<null>, #<null>, #<null>, #<null>, #<null>])

timholy added a commit that referenced this pull request Jul 15, 2024
Note this is a PR against #55047

Co-authored by: Gabriel Baraldi <[email protected]>
@JeffBezanson
Copy link
Sponsor Member Author

That should be fixed by integration with PackageCompiler. It knows how to do all that stuff.

JeffBezanson pushed a commit that referenced this pull request Jul 19, 2024
Note this is a PR against #55047

Co-authored by: Gabriel Baraldi <[email protected]>
IanButterworth added a commit that referenced this pull request Jul 19, 2024
I noticed this was an unrelated change in
#55047 so I thought I'd separate
it out
JeffBezanson pushed a commit that referenced this pull request Jul 23, 2024
Note this is a PR against #55047

Co-authored by: Gabriel Baraldi <[email protected]>
JeffBezanson added a commit that referenced this pull request Aug 1, 2024
A few more unobjectionable, NFC changes from #55047.
JeffBezanson pushed a commit that referenced this pull request Aug 2, 2024
Note this is a PR against #55047

Co-authored by: Gabriel Baraldi <[email protected]>
@JeffBezanson
Copy link
Sponsor Member Author

JeffBezanson commented Aug 2, 2024

OK, I deleted everything. Now this is barely 1000 lines. Don't worry it's all on a backup branch jb/gb/static-call-graph-backup. I converted the juliac machinery into a test case so we can focus here on just merging the core compiler functionality.

Now there are only a tiny number of controversial/weird changes:

  • unoptimize_throw_blocks: Can possibly be changed upstream? I believe we want to do that anyway. Otherwise the test case might need to build its own sysimage. (done by inference: remove throw block deoptimization completely #49260)
  • Linalg init changes: these are weird error cases and I think can just be changed not to use logging? Or again we build a custom sysimage for testing.
  • Setting max_args of printing functions: weird but harmless; can be upstreamed?

JeffBezanson pushed a commit that referenced this pull request Aug 8, 2024
Note this is a PR against #55047

Co-authored by: Gabriel Baraldi <[email protected]>
@JeffBezanson
Copy link
Sponsor Member Author

I think this is mergeable now so we can get the --trim option in. The only difficulties are basically in the tests and docs, which can be updated any time.

@oscardssmith oscardssmith added the kind:feature Indicates new feature / enhancement requests label Sep 27, 2024
@PallHaraldsson
Copy link
Contributor

PallHaraldsson commented Sep 28, 2024

Please merge this for 1.12. I'm very exited to try this (more) easily out, and for regular users to try, so would it be possible to add this to 1.11.0 or at least some 1.11.x point release (marked as an experimental new feature)? It seems this is largely not affecting regular Julia execution, lives in contrib, but I do see though some changes in codegen and staticdata.c, so I'm not sure how invasive this is, and I understand if this is too late considering RC4...

@MasonProtter
Copy link
Contributor

The v1.11 feature freeze was over half a year ago.

@oscardssmith oscardssmith merged commit 97ecdb8 into master Sep 28, 2024
8 checks passed
@oscardssmith oscardssmith deleted the jb/gb/static-call-graph branch September 28, 2024 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:trimming Issues with trimming functionality or PR's relevant to its performance/functionality kind:feature Indicates new feature / enhancement requests
Projects
None yet
Development

Successfully merging this pull request may close these issues.