xref: /aosp_15_r20/external/toolchain-utils/pgo_tools_rust/pgo_rust.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li#!/usr/bin/env python3
2*760c253cSXin Li# Copyright 2022 The ChromiumOS Authors
3*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
4*760c253cSXin Li# found in the LICENSE file.
5*760c253cSXin Li
6*760c253cSXin Li# pylint: disable=line-too-long
7*760c253cSXin Li
8*760c253cSXin Li"""Handle most aspects of creating and benchmarking PGO profiles for Rust.
9*760c253cSXin Li
10*760c253cSXin LiThis is meant to be done at Rust uprev time. Ultimately profdata files need
11*760c253cSXin Lito be placed at
12*760c253cSXin Li
13*760c253cSXin Ligs://chromeos-localmirror/distfiles/rust-pgo-{rust_version}-frontend.profdata{s}.tz
14*760c253cSXin Liand
15*760c253cSXin Ligs://chromeos-localmirror/distfiles/rust-pgo-{rust_version}-llvm.profdata{s}.tz
16*760c253cSXin Li
17*760c253cSXin LiHere {s} is an optional suffix to distinguish between profdata files on the same
18*760c253cSXin LiRust version.
19*760c253cSXin Li
20*760c253cSXin LiThe intended flow is that you first get the new Rust version in a shape so that
21*760c253cSXin Liit builds, for instance modifying or adding patches as necessary. Note that if
22*760c253cSXin Liyou need to generate manifests for dev-lang/rust and dev-lang/rust-host before
23*760c253cSXin Lithe profdata files are created, which will cause the `ebuild manifest` command
24*760c253cSXin Lito fail. One way to handle this is to temporarily delete the lines of the
25*760c253cSXin Livariable SRC_URI in cros-rustc.eclass which refer to profdata files.
26*760c253cSXin Li
27*760c253cSXin LiAfter you have a new working Rust version, you can run the following.
28*760c253cSXin Li
29*760c253cSXin Li```
30*760c253cSXin Li$ ./pgo_rust.py generate         # generate profdata files
31*760c253cSXin Li$ ./pgo_rust.py benchmark-pgo    # benchmark with PGO
32*760c253cSXin Li$ ./pgo_rust.py benchmark-nopgo  # benchmark without PGO
33*760c253cSXin Li$ ./pgo_rust.py upload-profdata  # upload profdata to localmirror
34*760c253cSXin Li```
35*760c253cSXin Li
36*760c253cSXin LiThe benchmark steps aren't strictly necessary, but are recommended and will
37*760c253cSXin Liupload benchmark data to
38*760c253cSXin Li
39*760c253cSXin Ligs://chromeos-toolchain-artifacts/rust-pgo/benchmarks/{rust_version}/
40*760c253cSXin Li
41*760c253cSXin LiCurrently by default ripgrep 13.0.0 is used as both the crate to build using an
42*760c253cSXin Liinstrumented Rust while generating profdata, and the crate to build to
43*760c253cSXin Libenchmark Rust. You may wish to experiment with other crates for either role.
44*760c253cSXin LiIn that case upload your crate to
45*760c253cSXin Li
46*760c253cSXin Ligs://chromeos-toolchain-artifacts/rust-pgo/crates/{name}-{version}.tar.xz
47*760c253cSXin Li
48*760c253cSXin Liand use `--crate-name` and `--crate-version` to indicate which crate to build
49*760c253cSXin Lito generate profdata (or which crate's generated profdata to use), and
50*760c253cSXin Li`--bench-crate-name` to indicate which crate to build in benchmarks.
51*760c253cSXin Li
52*760c253cSXin LiNotes on various local and GS locations follow.
53*760c253cSXin Li
54*760c253cSXin LiNote that currently we need to keep separate profdata files for the LLVM and
55*760c253cSXin Lifrontend components of Rust. This is because LLVM profdata is instrumented by
56*760c253cSXin Lithe system LLVM, but Rust's profdata is instrumented by its own LLVM, which
57*760c253cSXin Limay have separate profdata.
58*760c253cSXin Li
59*760c253cSXin Liprofdata files accessed by ebuilds must be stored in
60*760c253cSXin Li
61*760c253cSXin Ligs://chromeos-localmirror/distfiles
62*760c253cSXin Li
63*760c253cSXin LiSpecifically, they go to
64*760c253cSXin Li
65*760c253cSXin Ligs://chromeos-localmirror/distfiles/rust-pgo-{rust-version}-llvm.profdata.xz
66*760c253cSXin Li
67*760c253cSXin Ligs://chromeos-localmirror/distfiles/
68*760c253cSXin Li  rust-pgo-{rust-version}-frontend.profdata.xz
69*760c253cSXin Li
70*760c253cSXin LiBut we can store other data elsewhere, like gs://chromeos-toolchain-artifacts.
71*760c253cSXin Li
72*760c253cSXin LiGS locations:
73*760c253cSXin Li
74*760c253cSXin Li{GS_BASE}/crates/ - store crates we may use for generating profiles or
75*760c253cSXin Libenchmarking PGO optimized Rust compilers
76*760c253cSXin Li
77*760c253cSXin Li{GS_BASE}/benchmarks/{rust_version}/nopgo/
78*760c253cSXin Li  {bench_crate_name}-{bench_crate_version}-{triple}
79*760c253cSXin Li
80*760c253cSXin Li{GS_BASE}/benchmarks/{rust_version}/{crate_name}-{crate_version}/
81*760c253cSXin Li  {bench_crate_name}-{bench_crate_version}-{triple}
82*760c253cSXin Li
83*760c253cSXin LiLocal locations:
84*760c253cSXin Li
85*760c253cSXin Li{LOCAL_BASE}/crates/
86*760c253cSXin Li
87*760c253cSXin Li{LOCAL_BASE}/llvm-profraw/
88*760c253cSXin Li
89*760c253cSXin Li{LOCAL_BASE}/frontend-profraw/
90*760c253cSXin Li
91*760c253cSXin Li{LOCAL_BASE}/profdata/{crate_name}-{crate_version}/llvm.profdata
92*760c253cSXin Li
93*760c253cSXin Li{LOCAL_BASE}/profdata/{crate_name}-{crate_version}/frontend.profdata
94*760c253cSXin Li
95*760c253cSXin Li{LOCAL_BASE}/benchmarks/{rust_version}/nopgo/
96*760c253cSXin Li  {bench_crate_name}-{bench_crate_version}-{triple}
97*760c253cSXin Li
98*760c253cSXin Li{LOCAL_BASE}/benchmarks/{rust_version}/{crate_name}-{crate_version}/
99*760c253cSXin Li  {bench_crate_name}-{bench_crate_version}-{triple}
100*760c253cSXin Li
101*760c253cSXin Li{LOCAL_BASE}/llvm.profdata     - must go here to be used by Rust ebuild
102*760c253cSXin Li{LOCAL_BASE}/frontend.profdata - must go here to be used by Rust ebuild
103*760c253cSXin Li"""
104*760c253cSXin Li
105*760c253cSXin Liimport argparse
106*760c253cSXin Liimport contextlib
107*760c253cSXin Liimport logging
108*760c253cSXin Liimport os
109*760c253cSXin Lifrom pathlib import Path
110*760c253cSXin Lifrom pathlib import PurePosixPath
111*760c253cSXin Liimport re
112*760c253cSXin Liimport shutil
113*760c253cSXin Liimport subprocess
114*760c253cSXin Liimport sys
115*760c253cSXin Lifrom typing import cast, List, Mapping, Optional
116*760c253cSXin Li
117*760c253cSXin Li
118*760c253cSXin LiTARGET_TRIPLES = [
119*760c253cSXin Li    "x86_64-cros-linux-gnu",
120*760c253cSXin Li    "x86_64-pc-linux-gnu",
121*760c253cSXin Li    "armv7a-cros-linux-gnueabihf",
122*760c253cSXin Li    "aarch64-cros-linux-gnu",
123*760c253cSXin Li]
124*760c253cSXin Li
125*760c253cSXin LiLOCAL_BASE = Path("/tmp/rust-pgo")
126*760c253cSXin Li
127*760c253cSXin LiGS_BASE = PurePosixPath("/chromeos-toolchain-artifacts/rust-pgo")
128*760c253cSXin Li
129*760c253cSXin LiGS_DISTFILES = PurePosixPath("/chromeos-localmirror/distfiles")
130*760c253cSXin Li
131*760c253cSXin LiCRATE_NAME = "ripgrep"
132*760c253cSXin Li
133*760c253cSXin LiCRATE_VERSION = "13.0.0"
134*760c253cSXin Li
135*760c253cSXin Li
136*760c253cSXin Li@contextlib.contextmanager
137*760c253cSXin Lidef chdir(new_directory: Path):
138*760c253cSXin Li    initial_directory = Path.cwd()
139*760c253cSXin Li    os.chdir(new_directory)
140*760c253cSXin Li    try:
141*760c253cSXin Li        yield
142*760c253cSXin Li    finally:
143*760c253cSXin Li        os.chdir(initial_directory)
144*760c253cSXin Li
145*760c253cSXin Li
146*760c253cSXin Lidef run(
147*760c253cSXin Li    args: List,
148*760c253cSXin Li    *,
149*760c253cSXin Li    indent: int = 4,
150*760c253cSXin Li    env: Optional[Mapping[str, str]] = None,
151*760c253cSXin Li    capture_stdout: bool = False,
152*760c253cSXin Li    message: bool = True,
153*760c253cSXin Li) -> Optional[str]:
154*760c253cSXin Li    args = [str(arg) for arg in args]
155*760c253cSXin Li
156*760c253cSXin Li    if env is None:
157*760c253cSXin Li        new_env: Mapping[str, str] = os.environ
158*760c253cSXin Li    else:
159*760c253cSXin Li        new_env = os.environ.copy()
160*760c253cSXin Li        new_env.update(env)
161*760c253cSXin Li
162*760c253cSXin Li    if message:
163*760c253cSXin Li        if env is None:
164*760c253cSXin Li            logging.info("Running %s", args)
165*760c253cSXin Li        else:
166*760c253cSXin Li            logging.info("Running %s in environment %s", args, env)
167*760c253cSXin Li
168*760c253cSXin Li    result = subprocess.run(
169*760c253cSXin Li        args,
170*760c253cSXin Li        env=new_env,
171*760c253cSXin Li        stdout=subprocess.PIPE,
172*760c253cSXin Li        stderr=subprocess.PIPE,
173*760c253cSXin Li        encoding="utf-8",
174*760c253cSXin Li        check=False,
175*760c253cSXin Li    )
176*760c253cSXin Li
177*760c253cSXin Li    stdout = result.stdout
178*760c253cSXin Li    stderr = result.stderr
179*760c253cSXin Li    if indent != 0:
180*760c253cSXin Li        stdout = re.sub("^", " " * indent, stdout, flags=re.MULTILINE)
181*760c253cSXin Li        stderr = re.sub("^", " " * indent, stderr, flags=re.MULTILINE)
182*760c253cSXin Li
183*760c253cSXin Li    if capture_stdout:
184*760c253cSXin Li        ret = result.stdout
185*760c253cSXin Li    else:
186*760c253cSXin Li        logging.info("STDOUT:")
187*760c253cSXin Li        logging.info(stdout)
188*760c253cSXin Li        logging.info("STDERR:")
189*760c253cSXin Li        logging.info(stderr)
190*760c253cSXin Li        ret = None
191*760c253cSXin Li
192*760c253cSXin Li    result.check_returncode()
193*760c253cSXin Li
194*760c253cSXin Li    if message:
195*760c253cSXin Li        if env is None:
196*760c253cSXin Li            logging.info("Ran %s\n", args)
197*760c253cSXin Li        else:
198*760c253cSXin Li            logging.info("Ran %s in environment %s\n", args, env)
199*760c253cSXin Li
200*760c253cSXin Li    return ret
201*760c253cSXin Li
202*760c253cSXin Li
203*760c253cSXin Lidef get_command_output(args: List, **kwargs) -> str:
204*760c253cSXin Li    """Runs a command and returns its stdout and stderr as a string."""
205*760c253cSXin Li    return cast(str, run(args, capture_stdout=True, **kwargs))
206*760c253cSXin Li
207*760c253cSXin Li
208*760c253cSXin Lidef get_rust_version() -> str:
209*760c253cSXin Li    s = get_command_output(["rustc", "--version"])
210*760c253cSXin Li    m = re.search(r"\d+\.\d+\.\d+", s)
211*760c253cSXin Li    assert m is not None, repr(s)
212*760c253cSXin Li    return m.group(0)
213*760c253cSXin Li
214*760c253cSXin Li
215*760c253cSXin Lidef download_unpack_crate(*, crate_name: str, crate_version: str):
216*760c253cSXin Li    filename_no_extension = f"{crate_name}-{crate_version}"
217*760c253cSXin Li    gs_path = GS_BASE / "crates" / f"{filename_no_extension}.tar.xz"
218*760c253cSXin Li    local_path = LOCAL_BASE / "crates"
219*760c253cSXin Li    shutil.rmtree(
220*760c253cSXin Li        local_path / f"{crate_name}-{crate_version}", ignore_errors=True
221*760c253cSXin Li    )
222*760c253cSXin Li    with chdir(local_path):
223*760c253cSXin Li        run(["gsutil", "cp", f"gs:/{gs_path}", "."])
224*760c253cSXin Li        run(["tar", "xaf", f"{filename_no_extension}.tar.xz"])
225*760c253cSXin Li
226*760c253cSXin Li
227*760c253cSXin Lidef build_crate(
228*760c253cSXin Li    *,
229*760c253cSXin Li    crate_name: str,
230*760c253cSXin Li    crate_version: str,
231*760c253cSXin Li    target_triple: str,
232*760c253cSXin Li    time_file: Optional[str] = None,
233*760c253cSXin Li):
234*760c253cSXin Li    local_path = LOCAL_BASE / "crates" / f"{crate_name}-{crate_version}"
235*760c253cSXin Li    with chdir(local_path):
236*760c253cSXin Li        Path(".cargo").mkdir(exist_ok=True)
237*760c253cSXin Li        with open(".cargo/config.toml", "w", encoding="utf-8") as f:
238*760c253cSXin Li            f.write(
239*760c253cSXin Li                "\n".join(
240*760c253cSXin Li                    (
241*760c253cSXin Li                        "[source.crates-io]",
242*760c253cSXin Li                        'replace-with = "vendored-sources"',
243*760c253cSXin Li                        "",
244*760c253cSXin Li                        "[source.vendored-sources]",
245*760c253cSXin Li                        'directory = "vendor"',
246*760c253cSXin Li                        "",
247*760c253cSXin Li                        f"[target.{target_triple}]",
248*760c253cSXin Li                        f'linker = "{target_triple}-clang"',
249*760c253cSXin Li                        "",
250*760c253cSXin Li                        "[target.'cfg(all())']",
251*760c253cSXin Li                        "rustflags = [",
252*760c253cSXin Li                        '    "-Clto=thin",',
253*760c253cSXin Li                        '    "-Cembed-bitcode=yes",',
254*760c253cSXin Li                        "]",
255*760c253cSXin Li                    )
256*760c253cSXin Li                )
257*760c253cSXin Li            )
258*760c253cSXin Li
259*760c253cSXin Li        run(["cargo", "clean"])
260*760c253cSXin Li
261*760c253cSXin Li        cargo_cmd = ["cargo", "build", "--release", "--target", target_triple]
262*760c253cSXin Li
263*760c253cSXin Li        if time_file is None:
264*760c253cSXin Li            run(cargo_cmd)
265*760c253cSXin Li        else:
266*760c253cSXin Li            time_cmd = [
267*760c253cSXin Li                "/usr/bin/time",
268*760c253cSXin Li                f"--output={time_file}",
269*760c253cSXin Li                "--format=wall time (s) %e\nuser time (s) %U\nmax RSS %M\n",
270*760c253cSXin Li            ]
271*760c253cSXin Li            run(time_cmd + cargo_cmd)
272*760c253cSXin Li
273*760c253cSXin Li
274*760c253cSXin Lidef build_rust(
275*760c253cSXin Li    *,
276*760c253cSXin Li    generate_frontend_profile: bool = False,
277*760c253cSXin Li    generate_llvm_profile: bool = False,
278*760c253cSXin Li    use_frontend_profile: bool = False,
279*760c253cSXin Li    use_llvm_profile: bool = False,
280*760c253cSXin Li):
281*760c253cSXin Li    if use_frontend_profile or use_llvm_profile:
282*760c253cSXin Li        assert not generate_frontend_profile and not generate_llvm_profile, (
283*760c253cSXin Li            "Can't build a compiler to both use profile information "
284*760c253cSXin Li            "and generate it"
285*760c253cSXin Li        )
286*760c253cSXin Li
287*760c253cSXin Li    assert (
288*760c253cSXin Li        not generate_frontend_profile or not generate_llvm_profile
289*760c253cSXin Li    ), "Can't generate both frontend and LLVM profile information"
290*760c253cSXin Li
291*760c253cSXin Li    use = "-rust_profile_frontend_use -rust_profile_llvm_use "
292*760c253cSXin Li    if generate_frontend_profile:
293*760c253cSXin Li        use += "rust_profile_frontend_generate "
294*760c253cSXin Li    if generate_llvm_profile:
295*760c253cSXin Li        use += "rust_profile_llvm_generate "
296*760c253cSXin Li    if use_frontend_profile:
297*760c253cSXin Li        use += "rust_profile_frontend_use_local "
298*760c253cSXin Li    if use_llvm_profile:
299*760c253cSXin Li        use += "rust_profile_llvm_use_local "
300*760c253cSXin Li
301*760c253cSXin Li    env_use = os.getenv("USE", "").rstrip()
302*760c253cSXin Li    use = (env_use + " " + use).strip()
303*760c253cSXin Li    rust_cross_packages = [
304*760c253cSXin Li        f"cross-{x}/rust" for x in TARGET_TRIPLES if "-pc-linux-" not in x
305*760c253cSXin Li    ]
306*760c253cSXin Li
307*760c253cSXin Li    # -E to preserve environment variables like USE, FEATURES, etc.
308*760c253cSXin Li    run(
309*760c253cSXin Li        [
310*760c253cSXin Li            "sudo",
311*760c253cSXin Li            "-E",
312*760c253cSXin Li            "emerge",
313*760c253cSXin Li            "-j",
314*760c253cSXin Li            "dev-lang/rust-host",
315*760c253cSXin Li        ]
316*760c253cSXin Li        + rust_cross_packages,
317*760c253cSXin Li        env={"USE": use},
318*760c253cSXin Li    )
319*760c253cSXin Li
320*760c253cSXin Li
321*760c253cSXin Lidef merge_profdata(llvm_or_frontend, *, source_directory: Path, dest: Path):
322*760c253cSXin Li    assert llvm_or_frontend in ("llvm", "frontend")
323*760c253cSXin Li
324*760c253cSXin Li    # The two `llvm-profdata` programs come from different LLVM versions, and
325*760c253cSXin Li    # may support different versions of the profdata format, so make sure to
326*760c253cSXin Li    # use the right one.
327*760c253cSXin Li    llvm_profdata = (
328*760c253cSXin Li        "/usr/bin/llvm-profdata"
329*760c253cSXin Li        if llvm_or_frontend == "llvm"
330*760c253cSXin Li        else "/usr/libexec/rust/llvm-profdata"
331*760c253cSXin Li    )
332*760c253cSXin Li
333*760c253cSXin Li    dest.parent.mkdir(parents=True, exist_ok=True)
334*760c253cSXin Li
335*760c253cSXin Li    files = list(source_directory.glob("*.profraw"))
336*760c253cSXin Li    assert files, f"No profraw files found in {source_directory}"
337*760c253cSXin Li    run([llvm_profdata, "merge", f"--output={dest}"] + files)
338*760c253cSXin Li
339*760c253cSXin Li
340*760c253cSXin Lidef do_upload_profdata(*, source: Path, dest: PurePosixPath):
341*760c253cSXin Li    new_path = source.parent / (source.name + ".xz")
342*760c253cSXin Li    run(["xz", "--keep", "--compress", "--force", source])
343*760c253cSXin Li    upload_file(source=new_path, dest=dest, public_read=True)
344*760c253cSXin Li
345*760c253cSXin Li
346*760c253cSXin Lidef upload_file(
347*760c253cSXin Li    *, source: Path, dest: PurePosixPath, public_read: bool = False
348*760c253cSXin Li):
349*760c253cSXin Li    if public_read:
350*760c253cSXin Li        run(["gsutil", "cp", "-a", "public-read", source, f"gs:/{dest}"])
351*760c253cSXin Li    else:
352*760c253cSXin Li        run(["gsutil", "cp", source, f"gs:/{dest}"])
353*760c253cSXin Li
354*760c253cSXin Li
355*760c253cSXin Lidef maybe_download_crate(*, crate_name: str, crate_version: str):
356*760c253cSXin Li    """Downloads a crate if its download directory does not already exist."""
357*760c253cSXin Li    directory = LOCAL_BASE / "crates" / f"{crate_name}-{crate_version}"
358*760c253cSXin Li    if directory.is_dir():
359*760c253cSXin Li        logging.info("Crate already downloaded")
360*760c253cSXin Li    else:
361*760c253cSXin Li        logging.info("Downloading crate")
362*760c253cSXin Li        download_unpack_crate(
363*760c253cSXin Li            crate_name=crate_name, crate_version=crate_version
364*760c253cSXin Li        )
365*760c253cSXin Li
366*760c253cSXin Li
367*760c253cSXin Lidef generate(args):
368*760c253cSXin Li    maybe_download_crate(
369*760c253cSXin Li        crate_name=args.crate_name, crate_version=args.crate_version
370*760c253cSXin Li    )
371*760c253cSXin Li
372*760c253cSXin Li    llvm_dir = LOCAL_BASE / "llvm-profraw"
373*760c253cSXin Li    shutil.rmtree(llvm_dir, ignore_errors=True)
374*760c253cSXin Li    frontend_dir = LOCAL_BASE / "frontend-profraw"
375*760c253cSXin Li    shutil.rmtree(frontend_dir, ignore_errors=True)
376*760c253cSXin Li
377*760c253cSXin Li    logging.info("Building Rust instrumented for llvm")
378*760c253cSXin Li    build_rust(generate_llvm_profile=True)
379*760c253cSXin Li
380*760c253cSXin Li    llvm_dir.mkdir(parents=True, exist_ok=True)
381*760c253cSXin Li    for triple in TARGET_TRIPLES:
382*760c253cSXin Li        logging.info(
383*760c253cSXin Li            "Building crate with LLVM instrumentation, for triple %s", triple
384*760c253cSXin Li        )
385*760c253cSXin Li        build_crate(
386*760c253cSXin Li            crate_name=args.crate_name,
387*760c253cSXin Li            crate_version=args.crate_version,
388*760c253cSXin Li            target_triple=triple,
389*760c253cSXin Li        )
390*760c253cSXin Li
391*760c253cSXin Li    logging.info("Merging LLVM profile data")
392*760c253cSXin Li    merge_profdata(
393*760c253cSXin Li        "llvm",
394*760c253cSXin Li        source_directory=LOCAL_BASE / "llvm-profraw",
395*760c253cSXin Li        dest=(
396*760c253cSXin Li            LOCAL_BASE
397*760c253cSXin Li            / "profdata"
398*760c253cSXin Li            / f"{args.crate_name}-{args.crate_version}"
399*760c253cSXin Li            / "llvm.profdata"
400*760c253cSXin Li        ),
401*760c253cSXin Li    )
402*760c253cSXin Li
403*760c253cSXin Li    logging.info("Building Rust instrumented for frontend")
404*760c253cSXin Li    build_rust(generate_frontend_profile=True)
405*760c253cSXin Li
406*760c253cSXin Li    frontend_dir.mkdir(parents=True, exist_ok=True)
407*760c253cSXin Li    for triple in TARGET_TRIPLES:
408*760c253cSXin Li        logging.info(
409*760c253cSXin Li            "Building crate with frontend instrumentation, for triple %s",
410*760c253cSXin Li            triple,
411*760c253cSXin Li        )
412*760c253cSXin Li        build_crate(
413*760c253cSXin Li            crate_name=args.crate_name,
414*760c253cSXin Li            crate_version=args.crate_version,
415*760c253cSXin Li            target_triple=triple,
416*760c253cSXin Li        )
417*760c253cSXin Li
418*760c253cSXin Li    logging.info("Merging frontend profile data")
419*760c253cSXin Li    merge_profdata(
420*760c253cSXin Li        "frontend",
421*760c253cSXin Li        source_directory=LOCAL_BASE / "frontend-profraw",
422*760c253cSXin Li        dest=(
423*760c253cSXin Li            LOCAL_BASE
424*760c253cSXin Li            / "profdata"
425*760c253cSXin Li            / f"{args.crate_name}-{args.crate_version}"
426*760c253cSXin Li            / "frontend.profdata"
427*760c253cSXin Li        ),
428*760c253cSXin Li    )
429*760c253cSXin Li
430*760c253cSXin Li
431*760c253cSXin Lidef benchmark_nopgo(args):
432*760c253cSXin Li    maybe_download_crate(
433*760c253cSXin Li        crate_name=args.bench_crate_name, crate_version=args.bench_crate_version
434*760c253cSXin Li    )
435*760c253cSXin Li
436*760c253cSXin Li    logging.info("Building Rust, no PGO")
437*760c253cSXin Li    build_rust()
438*760c253cSXin Li
439*760c253cSXin Li    time_directory = LOCAL_BASE / "benchmarks" / "nopgo"
440*760c253cSXin Li    logging.info("Benchmarking crate build with no PGO")
441*760c253cSXin Li    time_directory.mkdir(parents=True, exist_ok=True)
442*760c253cSXin Li    for triple in TARGET_TRIPLES:
443*760c253cSXin Li        build_crate(
444*760c253cSXin Li            crate_name=args.bench_crate_name,
445*760c253cSXin Li            crate_version=args.bench_crate_version,
446*760c253cSXin Li            target_triple=triple,
447*760c253cSXin Li            time_file=(
448*760c253cSXin Li                time_directory
449*760c253cSXin Li                / f"{args.bench_crate_name}-{args.bench_crate_version}-{triple}"
450*760c253cSXin Li            ),
451*760c253cSXin Li        )
452*760c253cSXin Li
453*760c253cSXin Li    rust_version = get_rust_version()
454*760c253cSXin Li    dest_directory = (
455*760c253cSXin Li        GS_BASE / "benchmarks" / rust_version / f"nopgo{args.suffix}"
456*760c253cSXin Li    )
457*760c253cSXin Li    logging.info("Uploading benchmark data")
458*760c253cSXin Li    for file in time_directory.iterdir():
459*760c253cSXin Li        upload_file(
460*760c253cSXin Li            source=time_directory / file.name, dest=dest_directory / file.name
461*760c253cSXin Li        )
462*760c253cSXin Li
463*760c253cSXin Li
464*760c253cSXin Lidef benchmark_pgo(args):
465*760c253cSXin Li    maybe_download_crate(
466*760c253cSXin Li        crate_name=args.bench_crate_name, crate_version=args.bench_crate_version
467*760c253cSXin Li    )
468*760c253cSXin Li
469*760c253cSXin Li    files_dir = Path(
470*760c253cSXin Li        "/mnt/host/source/src/third_party/chromiumos-overlay",
471*760c253cSXin Li        "dev-lang/rust/files",
472*760c253cSXin Li    )
473*760c253cSXin Li
474*760c253cSXin Li    logging.info("Copying profile data to be used in building Rust")
475*760c253cSXin Li    run(
476*760c253cSXin Li        [
477*760c253cSXin Li            "cp",
478*760c253cSXin Li            (
479*760c253cSXin Li                LOCAL_BASE
480*760c253cSXin Li                / "profdata"
481*760c253cSXin Li                / f"{args.crate_name}-{args.crate_version}"
482*760c253cSXin Li                / "llvm.profdata"
483*760c253cSXin Li            ),
484*760c253cSXin Li            files_dir,
485*760c253cSXin Li        ]
486*760c253cSXin Li    )
487*760c253cSXin Li    run(
488*760c253cSXin Li        [
489*760c253cSXin Li            "cp",
490*760c253cSXin Li            (
491*760c253cSXin Li                LOCAL_BASE
492*760c253cSXin Li                / "profdata"
493*760c253cSXin Li                / f"{args.crate_name}-{args.crate_version}"
494*760c253cSXin Li                / "frontend.profdata"
495*760c253cSXin Li            ),
496*760c253cSXin Li            files_dir,
497*760c253cSXin Li        ]
498*760c253cSXin Li    )
499*760c253cSXin Li
500*760c253cSXin Li    logging.info("Building Rust with PGO")
501*760c253cSXin Li    build_rust(use_llvm_profile=True, use_frontend_profile=True)
502*760c253cSXin Li
503*760c253cSXin Li    time_directory = (
504*760c253cSXin Li        LOCAL_BASE / "benchmarks" / f"{args.crate_name}-{args.crate_version}"
505*760c253cSXin Li    )
506*760c253cSXin Li    time_directory.mkdir(parents=True, exist_ok=True)
507*760c253cSXin Li    logging.info("Benchmarking crate built with PGO")
508*760c253cSXin Li    for triple in TARGET_TRIPLES:
509*760c253cSXin Li        build_crate(
510*760c253cSXin Li            crate_name=args.bench_crate_name,
511*760c253cSXin Li            crate_version=args.bench_crate_version,
512*760c253cSXin Li            target_triple=triple,
513*760c253cSXin Li            time_file=(
514*760c253cSXin Li                time_directory
515*760c253cSXin Li                / f"{args.bench_crate_name}-{args.bench_crate_version}-{triple}"
516*760c253cSXin Li            ),
517*760c253cSXin Li        )
518*760c253cSXin Li
519*760c253cSXin Li    rust_version = get_rust_version()
520*760c253cSXin Li    dest_directory = (
521*760c253cSXin Li        GS_BASE
522*760c253cSXin Li        / "benchmarks"
523*760c253cSXin Li        / rust_version
524*760c253cSXin Li        / f"{args.crate_name}-{args.crate_version}{args.suffix}"
525*760c253cSXin Li    )
526*760c253cSXin Li    logging.info("Uploading benchmark data")
527*760c253cSXin Li    for file in time_directory.iterdir():
528*760c253cSXin Li        upload_file(
529*760c253cSXin Li            source=time_directory / file.name, dest=dest_directory / file.name
530*760c253cSXin Li        )
531*760c253cSXin Li
532*760c253cSXin Li
533*760c253cSXin Lidef upload_profdata(args):
534*760c253cSXin Li    directory = (
535*760c253cSXin Li        LOCAL_BASE / "profdata" / f"{args.crate_name}-{args.crate_version}"
536*760c253cSXin Li    )
537*760c253cSXin Li    rust_version = get_rust_version()
538*760c253cSXin Li
539*760c253cSXin Li    logging.info("Uploading LLVM profdata")
540*760c253cSXin Li    do_upload_profdata(
541*760c253cSXin Li        source=directory / "llvm.profdata",
542*760c253cSXin Li        dest=(
543*760c253cSXin Li            GS_DISTFILES
544*760c253cSXin Li            / f"rust-pgo-{rust_version}-llvm{args.suffix}.profdata.xz"
545*760c253cSXin Li        ),
546*760c253cSXin Li    )
547*760c253cSXin Li
548*760c253cSXin Li    logging.info("Uploading frontend profdata")
549*760c253cSXin Li    do_upload_profdata(
550*760c253cSXin Li        source=directory / "frontend.profdata",
551*760c253cSXin Li        dest=(
552*760c253cSXin Li            GS_DISTFILES
553*760c253cSXin Li            / f"rust-pgo-{rust_version}-frontend{args.suffix}.profdata.xz"
554*760c253cSXin Li        ),
555*760c253cSXin Li    )
556*760c253cSXin Li
557*760c253cSXin Li
558*760c253cSXin Lidef main(argv: List[str]) -> int:
559*760c253cSXin Li    logging.basicConfig(
560*760c253cSXin Li        stream=sys.stdout, level=logging.NOTSET, format="%(message)s"
561*760c253cSXin Li    )
562*760c253cSXin Li
563*760c253cSXin Li    parser = argparse.ArgumentParser(
564*760c253cSXin Li        prog=argv[0],
565*760c253cSXin Li        description=__doc__,
566*760c253cSXin Li        formatter_class=argparse.RawDescriptionHelpFormatter,
567*760c253cSXin Li    )
568*760c253cSXin Li    subparsers = parser.add_subparsers(dest="command", help="")
569*760c253cSXin Li    subparsers.required = True
570*760c253cSXin Li
571*760c253cSXin Li    parser_generate = subparsers.add_parser(
572*760c253cSXin Li        "generate",
573*760c253cSXin Li        help="Generate LLVM and frontend profdata files by building "
574*760c253cSXin Li        "instrumented Rust compilers, and using them to build the "
575*760c253cSXin Li        "indicated crate (downloading the crate if necessary).",
576*760c253cSXin Li    )
577*760c253cSXin Li    parser_generate.set_defaults(func=generate)
578*760c253cSXin Li    parser_generate.add_argument(
579*760c253cSXin Li        "--crate-name", default=CRATE_NAME, help="Name of the crate to build"
580*760c253cSXin Li    )
581*760c253cSXin Li    parser_generate.add_argument(
582*760c253cSXin Li        "--crate-version",
583*760c253cSXin Li        default=CRATE_VERSION,
584*760c253cSXin Li        help="Version of the crate to build",
585*760c253cSXin Li    )
586*760c253cSXin Li
587*760c253cSXin Li    parser_benchmark_nopgo = subparsers.add_parser(
588*760c253cSXin Li        "benchmark-nopgo",
589*760c253cSXin Li        help="Build the Rust compiler without PGO, benchmark "
590*760c253cSXin Li        "the build of the indicated crate, and upload "
591*760c253cSXin Li        "the benchmark data.",
592*760c253cSXin Li    )
593*760c253cSXin Li    parser_benchmark_nopgo.set_defaults(func=benchmark_nopgo)
594*760c253cSXin Li    parser_benchmark_nopgo.add_argument(
595*760c253cSXin Li        "--bench-crate-name",
596*760c253cSXin Li        default=CRATE_NAME,
597*760c253cSXin Li        help="Name of the crate whose build to benchmark",
598*760c253cSXin Li    )
599*760c253cSXin Li    parser_benchmark_nopgo.add_argument(
600*760c253cSXin Li        "--bench-crate-version",
601*760c253cSXin Li        default=CRATE_VERSION,
602*760c253cSXin Li        help="Version of the crate whose benchmark to build",
603*760c253cSXin Li    )
604*760c253cSXin Li    parser_benchmark_nopgo.add_argument(
605*760c253cSXin Li        "--suffix",
606*760c253cSXin Li        default="",
607*760c253cSXin Li        help="Suffix to distinguish benchmarks and profdata with identical "
608*760c253cSXin Li        "rustc versions",
609*760c253cSXin Li    )
610*760c253cSXin Li
611*760c253cSXin Li    parser_benchmark_pgo = subparsers.add_parser(
612*760c253cSXin Li        "benchmark-pgo",
613*760c253cSXin Li        help="Build the Rust compiler using PGO with the indicated "
614*760c253cSXin Li        "profdata files, benchmark the build of the indicated crate, "
615*760c253cSXin Li        "and upload the benchmark data.",
616*760c253cSXin Li    )
617*760c253cSXin Li    parser_benchmark_pgo.set_defaults(func=benchmark_pgo)
618*760c253cSXin Li    parser_benchmark_pgo.add_argument(
619*760c253cSXin Li        "--bench-crate-name",
620*760c253cSXin Li        default=CRATE_NAME,
621*760c253cSXin Li        help="Name of the crate whose build to benchmark",
622*760c253cSXin Li    )
623*760c253cSXin Li    parser_benchmark_pgo.add_argument(
624*760c253cSXin Li        "--bench-crate-version",
625*760c253cSXin Li        default=CRATE_VERSION,
626*760c253cSXin Li        help="Version of the crate whose benchmark to build",
627*760c253cSXin Li    )
628*760c253cSXin Li    parser_benchmark_pgo.add_argument(
629*760c253cSXin Li        "--crate-name",
630*760c253cSXin Li        default=CRATE_NAME,
631*760c253cSXin Li        help="Name of the crate whose profile to use",
632*760c253cSXin Li    )
633*760c253cSXin Li    parser_benchmark_pgo.add_argument(
634*760c253cSXin Li        "--crate-version",
635*760c253cSXin Li        default=CRATE_VERSION,
636*760c253cSXin Li        help="Version of the crate whose profile to use",
637*760c253cSXin Li    )
638*760c253cSXin Li    parser_benchmark_pgo.add_argument(
639*760c253cSXin Li        "--suffix",
640*760c253cSXin Li        default="",
641*760c253cSXin Li        help="Suffix to distinguish benchmarks and profdata with identical "
642*760c253cSXin Li        "rustc versions",
643*760c253cSXin Li    )
644*760c253cSXin Li
645*760c253cSXin Li    parser_upload_profdata = subparsers.add_parser(
646*760c253cSXin Li        "upload-profdata", help="Upload the profdata files"
647*760c253cSXin Li    )
648*760c253cSXin Li    parser_upload_profdata.set_defaults(func=upload_profdata)
649*760c253cSXin Li    parser_upload_profdata.add_argument(
650*760c253cSXin Li        "--crate-name",
651*760c253cSXin Li        default=CRATE_NAME,
652*760c253cSXin Li        help="Name of the crate whose profile to use",
653*760c253cSXin Li    )
654*760c253cSXin Li    parser_upload_profdata.add_argument(
655*760c253cSXin Li        "--crate-version",
656*760c253cSXin Li        default=CRATE_VERSION,
657*760c253cSXin Li        help="Version of the crate whose profile to use",
658*760c253cSXin Li    )
659*760c253cSXin Li    parser_upload_profdata.add_argument(
660*760c253cSXin Li        "--suffix",
661*760c253cSXin Li        default="",
662*760c253cSXin Li        help="Suffix to distinguish benchmarks and profdata with identical "
663*760c253cSXin Li        "rustc versions",
664*760c253cSXin Li    )
665*760c253cSXin Li
666*760c253cSXin Li    args = parser.parse_args(argv[1:])
667*760c253cSXin Li
668*760c253cSXin Li    (LOCAL_BASE / "crates").mkdir(parents=True, exist_ok=True)
669*760c253cSXin Li    (LOCAL_BASE / "llvm-profraw").mkdir(parents=True, exist_ok=True)
670*760c253cSXin Li    (LOCAL_BASE / "frontend-profraw").mkdir(parents=True, exist_ok=True)
671*760c253cSXin Li    (LOCAL_BASE / "benchmarks").mkdir(parents=True, exist_ok=True)
672*760c253cSXin Li
673*760c253cSXin Li    args.func(args)
674*760c253cSXin Li
675*760c253cSXin Li    return 0
676*760c253cSXin Li
677*760c253cSXin Li
678*760c253cSXin Liif __name__ == "__main__":
679*760c253cSXin Li    sys.exit(main(sys.argv))
680