1+ use anyhow:: Context ;
2+ use flate2:: { Compression , write:: GzEncoder } ;
3+ use std:: env:: consts:: EXE_EXTENSION ;
4+ use std:: ffi:: OsStr ;
15use std:: {
26 env,
37 fs:: File ,
48 io:: { self , BufWriter } ,
59 path:: { Path , PathBuf } ,
610} ;
7-
8- use flate2:: { Compression , write:: GzEncoder } ;
911use time:: OffsetDateTime ;
10- use xshell:: { Shell , cmd} ;
12+ use xshell:: { Cmd , Shell , cmd} ;
1113use zip:: { DateTime , ZipWriter , write:: SimpleFileOptions } ;
1214
1315use crate :: {
@@ -38,11 +40,18 @@ impl flags::Dist {
3840 // A hack to make VS Code prefer nightly over stable.
3941 format ! ( "{VERSION_NIGHTLY}.{patch_version}" )
4042 } ;
41- dist_server ( sh, & format ! ( "{version}-standalone" ) , & target, allocator, self . zig ) ?;
43+ dist_server (
44+ sh,
45+ & format ! ( "{version}-standalone" ) ,
46+ & target,
47+ allocator,
48+ self . zig ,
49+ self . pgo ,
50+ ) ?;
4251 let release_tag = if stable { date_iso ( sh) ? } else { "nightly" . to_owned ( ) } ;
4352 dist_client ( sh, & version, & release_tag, & target) ?;
4453 } else {
45- dist_server ( sh, "0.0.0-standalone" , & target, allocator, self . zig ) ?;
54+ dist_server ( sh, "0.0.0-standalone" , & target, allocator, self . zig , self . pgo ) ?;
4655 }
4756 Ok ( ( ) )
4857 }
@@ -84,6 +93,7 @@ fn dist_server(
8493 target : & Target ,
8594 allocator : Malloc ,
8695 zig : bool ,
96+ pgo : bool ,
8797) -> anyhow:: Result < ( ) > {
8898 let _e = sh. push_env ( "CFG_RELEASE" , release) ;
8999 let _e = sh. push_env ( "CARGO_PROFILE_RELEASE_LTO" , "thin" ) ;
@@ -100,7 +110,22 @@ fn dist_server(
100110 } ;
101111 let features = allocator. to_features ( ) ;
102112 let command = if linux_target && zig { "zigbuild" } else { "build" } ;
103- cmd ! ( sh, "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release" ) . run ( ) ?;
113+
114+ let pgo_profile = if pgo {
115+ Some ( gather_pgo_profile (
116+ sh,
117+ build_command ( sh, command, & target_name, features) ,
118+ & target_name,
119+ ) ?)
120+ } else {
121+ None
122+ } ;
123+
124+ let mut cmd = build_command ( sh, command, & target_name, features) ;
125+ if let Some ( profile) = pgo_profile {
126+ cmd = cmd. env ( "RUSTFLAGS" , format ! ( "-Cprofile-use={}" , profile. to_str( ) . unwrap( ) ) ) ;
127+ }
128+ cmd. run ( ) . context ( "cannot build Rust Analyzer" ) ?;
104129
105130 let dst = Path :: new ( "dist" ) . join ( & target. artifact_name ) ;
106131 if target_name. contains ( "-windows-" ) {
@@ -112,6 +137,70 @@ fn dist_server(
112137 Ok ( ( ) )
113138}
114139
140+ fn build_command < ' a > (
141+ sh : & ' a Shell ,
142+ command : & str ,
143+ target_name : & str ,
144+ features : & [ & str ] ,
145+ ) -> Cmd < ' a > {
146+ cmd ! (
147+ sh,
148+ "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release"
149+ )
150+ }
151+
152+ /// Decorates `ra_build_cmd` to add PGO instrumentation, and then runs the PGO instrumented
153+ /// Rust Analyzer on itself to gather a PGO profile.
154+ fn gather_pgo_profile < ' a > (
155+ sh : & ' a Shell ,
156+ ra_build_cmd : Cmd < ' a > ,
157+ target : & str ,
158+ ) -> anyhow:: Result < PathBuf > {
159+ let pgo_dir = std:: path:: absolute ( "ra-pgo-profiles" ) ?;
160+ // Clear out any stale profiles
161+ if pgo_dir. is_dir ( ) {
162+ std:: fs:: remove_dir_all ( & pgo_dir) ?;
163+ }
164+ std:: fs:: create_dir_all ( & pgo_dir) ?;
165+
166+ // Figure out a path to `llvm-profdata`
167+ let target_libdir = cmd ! ( sh, "rustc --print=target-libdir" )
168+ . read ( )
169+ . context ( "cannot resolve target-libdir from rustc" ) ?;
170+ let target_bindir = PathBuf :: from ( target_libdir) . parent ( ) . unwrap ( ) . join ( "bin" ) ;
171+ let llvm_profdata = target_bindir. join ( format ! ( "llvm-profdata{}" , EXE_EXTENSION ) ) ;
172+
173+ // Build RA with PGO instrumentation
174+ let cmd_gather =
175+ ra_build_cmd. env ( "RUSTFLAGS" , format ! ( "-Cprofile-generate={}" , pgo_dir. to_str( ) . unwrap( ) ) ) ;
176+ cmd_gather. run ( ) . context ( "cannot build rust-analyzer with PGO instrumentation" ) ?;
177+
178+ // Run RA on itself to gather profiles
179+ let train_crate = "." ;
180+ cmd ! (
181+ sh,
182+ "target/{target}/release/rust-analyzer analysis-stats {train_crate} --run-all-ide-things"
183+ )
184+ . run ( )
185+ . context ( "cannot generate PGO profiles" ) ?;
186+
187+ // Merge profiles into a single file
188+ let merged_profile = pgo_dir. join ( "merged.profdata" ) ;
189+ let profile_files = std:: fs:: read_dir ( pgo_dir) ?. filter_map ( |entry| {
190+ let entry = entry. ok ( ) ?;
191+ if entry. path ( ) . extension ( ) == Some ( OsStr :: new ( "profraw" ) ) {
192+ Some ( entry. path ( ) . to_str ( ) . unwrap ( ) . to_owned ( ) )
193+ } else {
194+ None
195+ }
196+ } ) ;
197+ cmd ! ( sh, "{llvm_profdata} merge {profile_files...} -o {merged_profile}" ) . run ( ) . context (
198+ "cannot merge PGO profiles. Do you have the rustup `llvm-tools` component installed?" ,
199+ ) ?;
200+
201+ Ok ( merged_profile)
202+ }
203+
115204fn gzip ( src_path : & Path , dest_path : & Path ) -> anyhow:: Result < ( ) > {
116205 let mut encoder = GzEncoder :: new ( File :: create ( dest_path) ?, Compression :: best ( ) ) ;
117206 let mut input = io:: BufReader :: new ( File :: open ( src_path) ?) ;
0 commit comments