diff --git a/src/bootstrap/bolt.rs b/src/bootstrap/bolt.rs deleted file mode 100644 index ea37cd47049bf..0000000000000 --- a/src/bootstrap/bolt.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::path::Path; -use std::process::Command; - -/// Uses the `llvm-bolt` binary to instrument the binary/library at the given `path` with BOLT. -/// When the instrumented artifact is executed, it will generate BOLT profiles into -/// `/tmp/prof.fdata..fdata`. -pub fn instrument_with_bolt_inplace(path: &Path) { - let dir = std::env::temp_dir(); - let instrumented_path = dir.join("instrumented.so"); - - let status = Command::new("llvm-bolt") - .arg("-instrument") - .arg(&path) - // Make sure that each process will write its profiles into a separate file - .arg("--instrumentation-file-append-pid") - .arg("-o") - .arg(&instrumented_path) - .status() - .expect("Could not instrument artifact using BOLT"); - - if !status.success() { - panic!("Could not instrument {} with BOLT, exit code {:?}", path.display(), status.code()); - } - - std::fs::copy(&instrumented_path, path).expect("Cannot copy instrumented artifact"); - std::fs::remove_file(instrumented_path).expect("Cannot delete instrumented artifact"); -} - -/// Uses the `llvm-bolt` binary to optimize the binary/library at the given `path` with BOLT, -/// using merged profiles from `profile_path`. -/// -/// The recorded profiles have to be merged using the `merge-fdata` tool from LLVM and the merged -/// profile path should be then passed to this function. -pub fn optimize_library_with_bolt_inplace(path: &Path, profile_path: &Path) { - let dir = std::env::temp_dir(); - let optimized_path = dir.join("optimized.so"); - - let status = Command::new("llvm-bolt") - .arg(&path) - .arg("-data") - .arg(&profile_path) - .arg("-o") - .arg(&optimized_path) - // Reorder basic blocks within functions - .arg("-reorder-blocks=ext-tsp") - // Reorder functions within the binary - .arg("-reorder-functions=hfsort+") - // Split function code into hot and code regions - .arg("-split-functions=2") - // Split as many basic blocks as possible - .arg("-split-all-cold") - // Move jump tables to a separate section - .arg("-jump-tables=move") - // Use GNU_STACK program header for new segment (workaround for issues with strip/objcopy) - .arg("-use-gnu-stack") - // Fold functions with identical code - .arg("-icf=1") - // Update DWARF debug info in the final binary - .arg("-update-debug-sections") - // Print optimization statistics - .arg("-dyno-stats") - .status() - .expect("Could not optimize artifact using BOLT"); - - if !status.success() { - panic!("Could not optimize {} with BOLT, exit code {:?}", path.display(), status.code()); - } - - std::fs::copy(&optimized_path, path).expect("Cannot copy optimized artifact"); - std::fs::remove_file(optimized_path).expect("Cannot delete optimized artifact"); -} diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index 165502b0a41d8..d86b3ec96679f 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -172,8 +172,6 @@ pub struct Config { pub llvm_profile_use: Option, pub llvm_profile_generate: bool, pub llvm_libunwind_default: Option, - pub llvm_bolt_profile_generate: bool, - pub llvm_bolt_profile_use: Option, pub build: TargetSelection, pub hosts: Vec, @@ -869,15 +867,6 @@ impl Config { } config.llvm_profile_use = flags.llvm_profile_use; config.llvm_profile_generate = flags.llvm_profile_generate; - config.llvm_bolt_profile_generate = flags.llvm_bolt_profile_generate; - config.llvm_bolt_profile_use = flags.llvm_bolt_profile_use; - - if config.llvm_bolt_profile_generate && config.llvm_bolt_profile_use.is_some() { - eprintln!( - "Cannot use both `llvm_bolt_profile_generate` and `llvm_bolt_profile_use` at the same time" - ); - crate::detail_exit(1); - } // Infer the rest of the configuration. diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 02e35d2436e2f..1a3f1b81c251e 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -2220,10 +2220,6 @@ impl Step for ReproducibleArtifacts { tarball.add_file(path, ".", 0o644); added_anything = true; } - if let Some(path) = builder.config.llvm_bolt_profile_use.as_ref() { - tarball.add_file(path, ".", 0o644); - added_anything = true; - } if added_anything { Some(tarball.generate()) } else { None } } } diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs index 52c3dc0bf7591..6cd1da6a849dc 100644 --- a/src/bootstrap/flags.rs +++ b/src/bootstrap/flags.rs @@ -78,8 +78,6 @@ pub struct Flags { // // llvm_out/build/profiles/ is the location this writes to. pub llvm_profile_generate: bool, - pub llvm_bolt_profile_generate: bool, - pub llvm_bolt_profile_use: Option, } #[derive(Debug)] @@ -259,8 +257,6 @@ To learn more about a subcommand, run `./x.py -h`", opts.optmulti("D", "", "deny certain clippy lints", "OPT"); opts.optmulti("W", "", "warn about certain clippy lints", "OPT"); opts.optmulti("F", "", "forbid certain clippy lints", "OPT"); - opts.optflag("", "llvm-bolt-profile-generate", "generate BOLT profile for LLVM build"); - opts.optopt("", "llvm-bolt-profile-use", "use BOLT profile for LLVM build", "PROFILE"); // We can't use getopt to parse the options until we have completed specifying which // options are valid, but under the current implementation, some options are conditional on @@ -704,8 +700,6 @@ Arguments: rust_profile_generate: matches.opt_str("rust-profile-generate"), llvm_profile_use: matches.opt_str("llvm-profile-use"), llvm_profile_generate: matches.opt_present("llvm-profile-generate"), - llvm_bolt_profile_generate: matches.opt_present("llvm-bolt-profile-generate"), - llvm_bolt_profile_use: matches.opt_str("llvm-bolt-profile-use"), } } } diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index 267aa3278d8ff..22359fd0a299a 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -125,7 +125,6 @@ use crate::util::{ exe, libdir, mtime, output, run, run_suppressed, symlink_dir, try_run_suppressed, }; -mod bolt; mod builder; mod cache; mod cc_detect; diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs index 3acc2d4b5c4b1..20b2a72d1bb1e 100644 --- a/src/bootstrap/native.rs +++ b/src/bootstrap/native.rs @@ -16,7 +16,6 @@ use std::io; use std::path::{Path, PathBuf}; use std::process::Command; -use crate::bolt::{instrument_with_bolt_inplace, optimize_library_with_bolt_inplace}; use crate::builder::{Builder, RunConfig, ShouldRun, Step}; use crate::channel; use crate::config::{Config, TargetSelection}; @@ -345,12 +344,6 @@ impl Step for Llvm { if let Some(path) = builder.config.llvm_profile_use.as_ref() { cfg.define("LLVM_PROFDATA_FILE", &path); } - if builder.config.llvm_bolt_profile_generate - || builder.config.llvm_bolt_profile_use.is_some() - { - // Relocations are required for BOLT to work. - ldflags.push_all("-Wl,-q"); - } // Disable zstd to avoid a dependency on libzstd.so. cfg.define("LLVM_ENABLE_ZSTD", "OFF"); @@ -520,34 +513,12 @@ impl Step for Llvm { } } - // After LLVM is built, we modify (instrument or optimize) the libLLVM.so library file - // in place. This is fine, because currently we do not support incrementally rebuilding - // LLVM after a configuration change, so to rebuild it the build files have to be removed, - // which will also remove these modified files. - if builder.config.llvm_bolt_profile_generate { - instrument_with_bolt_inplace(&get_built_llvm_lib_path(&res.llvm_config)); - } - if let Some(path) = &builder.config.llvm_bolt_profile_use { - optimize_library_with_bolt_inplace( - &get_built_llvm_lib_path(&res.llvm_config), - &Path::new(path), - ); - } - t!(stamp.write()); res } } -/// Returns path to a built LLVM library (libLLVM.so). -/// Assumes that we have built LLVM into a single library file. -fn get_built_llvm_lib_path(llvm_config_path: &Path) -> PathBuf { - let mut cmd = Command::new(llvm_config_path); - cmd.arg("--libfiles"); - PathBuf::from(output(&mut cmd).trim()) -} - fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) { if !builder.config.llvm_version_check { return; diff --git a/src/ci/pgo.sh b/src/ci/pgo.sh deleted file mode 100755 index cbe32920a7458..0000000000000 --- a/src/ci/pgo.sh +++ /dev/null @@ -1,230 +0,0 @@ -#!/bin/bash -# ignore-tidy-linelength - -set -euxo pipefail - -ci_dir=`cd $(dirname $0) && pwd` -source "$ci_dir/shared.sh" - -# The root checkout, where the source is located -CHECKOUT=/checkout - -DOWNLOADED_LLVM=/rustroot - -# The main directory where the build occurs, which can be different between linux and windows -BUILD_ROOT=$CHECKOUT/obj - -if isWindows; then - CHECKOUT=$(pwd) - DOWNLOADED_LLVM=$CHECKOUT/citools/clang-rust - BUILD_ROOT=$CHECKOUT -fi - -# The various build artifacts used in other commands: to launch rustc builds, build the perf -# collector, and run benchmarks to gather profiling data -BUILD_ARTIFACTS=$BUILD_ROOT/build/$PGO_HOST -RUSTC_STAGE_0=$BUILD_ARTIFACTS/stage0/bin/rustc -CARGO_STAGE_0=$BUILD_ARTIFACTS/stage0/bin/cargo -RUSTC_STAGE_2=$BUILD_ARTIFACTS/stage2/bin/rustc - -# Windows needs these to have the .exe extension -if isWindows; then - RUSTC_STAGE_0="${RUSTC_STAGE_0}.exe" - CARGO_STAGE_0="${CARGO_STAGE_0}.exe" - RUSTC_STAGE_2="${RUSTC_STAGE_2}.exe" -fi - -# Make sure we have a temporary PGO work folder -PGO_TMP=/tmp/tmp-pgo -mkdir -p $PGO_TMP -rm -rf $PGO_TMP/* - -RUSTC_PERF=$PGO_TMP/rustc-perf - -# Compile several crates to gather execution PGO profiles. -# Arg0 => profiles (Debug, Opt) -# Arg1 => scenarios (Full, IncrFull, All) -# Arg2 => crates (syn, cargo, ...) -gather_profiles () { - cd $BUILD_ROOT - - # Compile libcore, both in opt-level=0 and opt-level=3 - RUSTC_BOOTSTRAP=1 $RUSTC_STAGE_2 \ - --edition=2021 --crate-type=lib $CHECKOUT/library/core/src/lib.rs \ - --out-dir $PGO_TMP - RUSTC_BOOTSTRAP=1 $RUSTC_STAGE_2 \ - --edition=2021 --crate-type=lib -Copt-level=3 $CHECKOUT/library/core/src/lib.rs \ - --out-dir $PGO_TMP - - cd $RUSTC_PERF - - # Run rustc-perf benchmarks - # Benchmark using profile_local with eprintln, which essentially just means - # don't actually benchmark -- just make sure we run rustc a bunch of times. - RUST_LOG=collector=debug \ - RUSTC=$RUSTC_STAGE_0 \ - RUSTC_BOOTSTRAP=1 \ - $CARGO_STAGE_0 run -p collector --bin collector -- \ - profile_local \ - eprintln \ - $RUSTC_STAGE_2 \ - --id Test \ - --profiles $1 \ - --cargo $CARGO_STAGE_0 \ - --scenarios $2 \ - --include $3 - - cd $BUILD_ROOT -} - -# This path has to be absolute -LLVM_PROFILE_DIRECTORY_ROOT=$PGO_TMP/llvm-pgo - -# We collect LLVM profiling information and rustc profiling information in -# separate phases. This increases build time -- though not by a huge amount -- -# but prevents any problems from arising due to different profiling runtimes -# being simultaneously linked in. -# LLVM IR PGO does not respect LLVM_PROFILE_FILE, so we have to set the profiling file -# path through our custom environment variable. We include the PID in the directory path -# to avoid updates to profile files being lost because of race conditions. -LLVM_PROFILE_DIR=${LLVM_PROFILE_DIRECTORY_ROOT}/prof-%p python3 $CHECKOUT/x.py build \ - --target=$PGO_HOST \ - --host=$PGO_HOST \ - --stage 2 library/std \ - --llvm-profile-generate - -# Compile rustc-perf: -# - get the expected commit source code: on linux, the Dockerfile downloads a source archive before -# running this script. On Windows, we do that here. -if isLinux; then - cp -r /tmp/rustc-perf $RUSTC_PERF - chown -R $(whoami): $RUSTC_PERF -else - # rustc-perf version from 2022-07-22 - PERF_COMMIT=3c253134664fdcba862c539d37f0de18557a9a4c - retry curl -LS -o $PGO_TMP/perf.zip \ - https://github.com/rust-lang/rustc-perf/archive/$PERF_COMMIT.zip && \ - cd $PGO_TMP && unzip -q perf.zip && \ - mv rustc-perf-$PERF_COMMIT $RUSTC_PERF && \ - rm perf.zip -fi - -# - build rustc-perf's collector ahead of time, which is needed to make sure the rustc-fake binary -# used by the collector is present. -cd $RUSTC_PERF - -RUSTC=$RUSTC_STAGE_0 \ -RUSTC_BOOTSTRAP=1 \ -$CARGO_STAGE_0 build -p collector - -# Here we're profiling LLVM, so we only care about `Debug` and `Opt`, because we want to stress -# codegen. We also profile some of the most prolific crates. -gather_profiles "Debug,Opt" "Full" \ - "syn-1.0.89,cargo-0.60.0,serde-1.0.136,ripgrep-13.0.0,regex-1.5.5,clap-3.1.6,hyper-0.14.18" - -LLVM_PROFILE_MERGED_FILE=$PGO_TMP/llvm-pgo.profdata - -# Merge the profile data we gathered for LLVM -# Note that this uses the profdata from the clang we used to build LLVM, -# which likely has a different version than our in-tree clang. -$DOWNLOADED_LLVM/bin/llvm-profdata merge -o ${LLVM_PROFILE_MERGED_FILE} ${LLVM_PROFILE_DIRECTORY_ROOT} - -echo "LLVM PGO statistics" -du -sh ${LLVM_PROFILE_MERGED_FILE} -du -sh ${LLVM_PROFILE_DIRECTORY_ROOT} -echo "Profile file count" -find ${LLVM_PROFILE_DIRECTORY_ROOT} -type f | wc -l - -# We don't need the individual .profraw files now that they have been merged into a final .profdata -rm -r $LLVM_PROFILE_DIRECTORY_ROOT - -# Rustbuild currently doesn't support rebuilding LLVM when PGO options -# change (or any other llvm-related options); so just clear out the relevant -# directories ourselves. -rm -r $BUILD_ARTIFACTS/llvm $BUILD_ARTIFACTS/lld - -# Okay, LLVM profiling is done, switch to rustc PGO. - -# The path has to be absolute -RUSTC_PROFILE_DIRECTORY_ROOT=$PGO_TMP/rustc-pgo - -python3 $CHECKOUT/x.py build --target=$PGO_HOST --host=$PGO_HOST \ - --stage 2 library/std \ - --rust-profile-generate=${RUSTC_PROFILE_DIRECTORY_ROOT} - -# Here we're profiling the `rustc` frontend, so we also include `Check`. -# The benchmark set includes various stress tests that put the frontend under pressure. -if isLinux; then - # The profile data is written into a single filepath that is being repeatedly merged when each - # rustc invocation ends. Empirically, this can result in some profiling data being lost. That's - # why we override the profile path to include the PID. This will produce many more profiling - # files, but the resulting profile will produce a slightly faster rustc binary. - LLVM_PROFILE_FILE=${RUSTC_PROFILE_DIRECTORY_ROOT}/default_%m_%p.profraw gather_profiles \ - "Check,Debug,Opt" "All" \ - "externs,ctfe-stress-5,cargo-0.60.0,token-stream-stress,match-stress,tuple-stress,diesel-1.4.8,bitmaps-3.1.0" -else - # On windows, we don't do that yet (because it generates a lot of data, hitting disk space - # limits on the builder), and use the default profraw merging behavior. - gather_profiles \ - "Check,Debug,Opt" "All" \ - "externs,ctfe-stress-5,cargo-0.60.0,token-stream-stress,match-stress,tuple-stress,diesel-1.4.8,bitmaps-3.1.0" -fi - -RUSTC_PROFILE_MERGED_FILE=$PGO_TMP/rustc-pgo.profdata - -# Merge the profile data we gathered -$BUILD_ARTIFACTS/llvm/bin/llvm-profdata \ - merge -o ${RUSTC_PROFILE_MERGED_FILE} ${RUSTC_PROFILE_DIRECTORY_ROOT} - -echo "Rustc PGO statistics" -du -sh ${RUSTC_PROFILE_MERGED_FILE} -du -sh ${RUSTC_PROFILE_DIRECTORY_ROOT} -echo "Profile file count" -find ${RUSTC_PROFILE_DIRECTORY_ROOT} -type f | wc -l - -# We don't need the individual .profraw files now that they have been merged into a final .profdata -rm -r $RUSTC_PROFILE_DIRECTORY_ROOT - -# Rustbuild currently doesn't support rebuilding LLVM when PGO options -# change (or any other llvm-related options); so just clear out the relevant -# directories ourselves. -rm -r $BUILD_ARTIFACTS/llvm $BUILD_ARTIFACTS/lld - -if isLinux; then - # Gather BOLT profile (BOLT is currently only available on Linux) - python3 ../x.py build --target=$PGO_HOST --host=$PGO_HOST \ - --stage 2 library/std \ - --llvm-profile-use=${LLVM_PROFILE_MERGED_FILE} \ - --llvm-bolt-profile-generate - - BOLT_PROFILE_MERGED_FILE=/tmp/bolt.profdata - - # Here we're profiling Bolt. - gather_profiles "Check,Debug,Opt" "Full" \ - "syn-1.0.89,serde-1.0.136,ripgrep-13.0.0,regex-1.5.5,clap-3.1.6,hyper-0.14.18" - - merge-fdata /tmp/prof.fdata* > ${BOLT_PROFILE_MERGED_FILE} - - echo "BOLT statistics" - du -sh /tmp/prof.fdata* - du -sh ${BOLT_PROFILE_MERGED_FILE} - echo "Profile file count" - find /tmp/prof.fdata* -type f | wc -l - - rm -r $BUILD_ARTIFACTS/llvm $BUILD_ARTIFACTS/lld - - # This produces the actual final set of artifacts, using both the LLVM and rustc - # collected profiling data. - $@ \ - --rust-profile-use=${RUSTC_PROFILE_MERGED_FILE} \ - --llvm-profile-use=${LLVM_PROFILE_MERGED_FILE} \ - --llvm-bolt-profile-use=${BOLT_PROFILE_MERGED_FILE} -else - $@ \ - --rust-profile-use=${RUSTC_PROFILE_MERGED_FILE} \ - --llvm-profile-use=${LLVM_PROFILE_MERGED_FILE} -fi - -echo "Rustc binary size" -ls -la ./build/$PGO_HOST/stage2/bin -ls -la ./build/$PGO_HOST/stage2/lib diff --git a/src/ci/stage-build.py b/src/ci/stage-build.py index c373edfcf46a2..8baccbf7e8328 100644 --- a/src/ci/stage-build.py +++ b/src/ci/stage-build.py @@ -80,6 +80,9 @@ def cargo_stage_0(self) -> Path: def rustc_stage_2(self) -> Path: return self.build_artifacts() / "stage2" / "bin" / "rustc" + def stage2_lib_llvm(self) -> Path: + raise NotImplementedError() + def opt_artifacts(self) -> Path: raise NotImplementedError @@ -127,6 +130,12 @@ def downloaded_llvm_dir(self) -> Path: def build_root(self) -> Path: return self.checkout_path() / "obj" + def stage2_lib_llvm(self) -> Path: + stage2_lib_dir = self.build_artifacts() / "stage2" / "lib" + lib_llvms = list(stage2_lib_dir.glob("libLLVM*")) + assert len(lib_llvms) == 1, "There should be exactly one libLLVM file found" + return lib_llvms[0] + def opt_artifacts(self) -> Path: return Path("/tmp/tmp-multistage/opt-artifacts") @@ -585,7 +594,7 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L pipeline.build_rustc_perf() # Stage 1: Build rustc + PGO instrumented LLVM - with timer.stage("Build rustc (LLVM PGO)"): + with timer.stage("Build rustc (LLVM PGO generate)"): build_rustc(pipeline, args=[ "--llvm-profile-generate" ], env=dict( @@ -602,7 +611,7 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L ] # Stage 2: Build PGO instrumented rustc + LLVM - with timer.stage("Build rustc (rustc PGO)"): + with timer.stage("Build rustc (rustc PGO generate)"): build_rustc(pipeline, args=[ "--rust-profile-generate", pipeline.rustc_profile_dir_root() @@ -617,27 +626,71 @@ def execute_build_pipeline(timer: Timer, pipeline: Pipeline, final_build_args: L pipeline.rustc_profile_merged_file() ] - # Stage 3: Build rustc + BOLT instrumented LLVM + # Bolt requires relocations. + env = {} if pipeline.supports_bolt(): - with timer.stage("Build rustc (LLVM BOLT)"): - build_rustc(pipeline, args=[ - "--llvm-profile-use", - pipeline.llvm_profile_merged_file(), - "--llvm-bolt-profile-generate", + env["LDFLAGS"] = "-Wl,-q" + + # Stage 3: Build PGO optimized rustc + PGO optimized LLVM + with timer.stage("Build rustc (rustc PGO use, LLVM PGO use)"): + build_rustc(pipeline, [ + "--llvm-profile-use", pipeline.llvm_profile_merged_file(), + "--rust-profile-use", pipeline.rustc_profile_merged_file(), + ], env) + + # Stage 4: BOLT optimize LLVM. + if pipeline.supports_bolt(): + # Instrument the stage2 libLLVM... + stage2_lib_llvm = pipeline.stage2_lib_llvm() + # ...and write the optimized one into the LLVM build directory, + # because we'll copy it from there when reassembling the stage2 compiler. + build_lib_llvm = pipeline.build_artifacts() / "llvm" / "build" / "lib" / stage2_lib_llvm.name + + # Back up the original libLLVM shared object. + orig_lib_llvm = pipeline.opt_artifacts() / "libLLVM.orig" + shutil.move(stage2_lib_llvm, orig_lib_llvm) + + with timer.stage("Bolt instrument LLVM"): + cmd([ + "llvm-bolt", "-instrument", orig_lib_llvm, + # Make sure that each process will write its profiles into a separate file + "--instrumentation-file-append-pid", + "-o", stage2_lib_llvm ]) + with timer.stage("Gather profiles (LLVM BOLT)"): gather_llvm_bolt_profiles(pipeline) - clear_llvm_files(pipeline) - final_build_args += [ - "--llvm-bolt-profile-use", - pipeline.llvm_bolt_profile_merged_file() - ] - - # Stage 4: Build PGO optimized rustc + PGO/BOLT optimized LLVM - with timer.stage("Final build"): - cmd(final_build_args) + with timer.stage("Bolt optimize LLVM"): + cmd([ + "llvm-bolt", orig_lib_llvm, + "-data", pipeline.llvm_bolt_profile_merged_file(), + "-o", build_lib_llvm, + # Reorder basic blocks within functions + "-reorder-blocks=ext-tsp", + # Reorder functions within the binary + "-reorder-functions=hfsort+", + # Split function code into hot and code regions + "-split-functions=2", + # Split as many basic blocks as possible + "-split-all-cold", + # Move jump tables to a separate section + "-jump-tables=move", + # Use GNU_STACK program header for new segment (workaround for issues with + # strip/objcopy) + "-use-gnu-stack", + # Fold functions with identical code + "-icf=1", + # Update DWARF debug info in the final binary + "-update-debug-sections", + # Print optimization statistics + "-dyno-stats", + ]) + # Stage 5: Execute the orinal dist command. + # This is supposed to reuse the already built rustc and LLVM. + with timer.stage("Dist rustc"): + cmd(final_build_args, env) if __name__ == "__main__": logging.basicConfig(