blob: 6699dfe20d1145a55fac4174ac115028bd89e557 [file] [log] [blame]
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This script generates a PGO profile for llvm-next.
Do not run it inside of a chroot. It establishes a chroot of its own.
"""
import argparse
import dataclasses
import logging
from pathlib import Path
import re
import shlex
import shutil
from typing import List
from llvm_tools import chroot
from llvm_tools import get_llvm_hash
from pgo_tools import pgo_utils
SDK_VERSION_CONF_SUBDIR = (
Path("src")
/ "third_party"
/ "chromiumos-overlay"
/ "chromeos"
/ "binhost"
/ "host"
/ "sdk_version.conf"
)
@dataclasses.dataclass(frozen=True)
class ChrootInfo:
"""Describes a unique chroot, and the SDK version to pin it to."""
chroot_name: str
out_dir_name: str
sdk_version: str
def detect_bootstrap_sdk_version(repo_root: Path) -> str:
sdk_version_conf = repo_root / SDK_VERSION_CONF_SUBDIR
bootstrap_version_re = re.compile(
r'^BOOTSTRAP_FROZEN_VERSION="([^"]+)"$',
re.MULTILINE,
)
results = bootstrap_version_re.findall(
sdk_version_conf.read_text(encoding="utf-8")
)
if len(results) != 1:
raise ValueError(
f"Expected exactly one match in {sdk_version_conf} for "
f"{bootstrap_version_re}; found {len(results)}"
)
return results[0]
def create_fresh_chroot(
repo_root: Path,
chroot_info: ChrootInfo,
):
"""Creates a chroot. If it already exists, replaces it."""
pgo_utils.run(
[
"cros_sdk",
"--replace",
f"--chroot={chroot_info.chroot_name}",
f"--out-dir={chroot_info.out_dir_name}",
f"--sdk-version={chroot_info.sdk_version}",
"--",
"true",
],
cwd=repo_root,
)
def generate_pgo_profile(
*,
repo_root: Path,
chroot_info: ChrootInfo,
chroot_output_file: Path,
sha: str,
clean_llvm: bool,
):
"""Generates a PGO profile to `chroot_output_file`."""
cros_sdk: pgo_utils.Command = [
"cros_sdk",
f"--chroot={chroot_info.chroot_name}",
f"--out-dir={chroot_info.out_dir_name}",
f"--sdk-version={chroot_info.sdk_version}",
"--",
]
toolchain_utils_bin = (
"/mnt/host/source/src/third_party/toolchain-utils/py/bin"
)
setup_for_workon_cmd = cros_sdk + [
f"{toolchain_utils_bin}/llvm_tools/setup_for_workon.py",
f"--checkout={sha}",
"--package=sys-devel/llvm",
]
if clean_llvm:
setup_for_workon_cmd.append("--clean-llvm")
pgo_utils.run(
setup_for_workon_cmd,
cwd=repo_root,
)
pgo_utils.run(
cros_sdk
+ [
"cros-workon",
"--host",
"start",
"sys-devel/llvm",
],
cwd=repo_root,
)
pgo_utils.run(
cros_sdk
+ [
f"{toolchain_utils_bin}/pgo_tools/generate_pgo_profile.py",
f"--output={chroot_output_file}",
],
cwd=repo_root,
)
def compress_pgo_profile(pgo_profile: Path) -> Path:
"""Compresses a PGO profile for upload to gs://."""
pgo_utils.run(
["xz", "-9", "-k", pgo_profile],
)
return Path(str(pgo_profile) + ".xz")
def translate_chroot_path_to_out_of_chroot(
repo_root: Path, path: Path, info: ChrootInfo
) -> Path:
"""Translates a chroot path into an out-of-chroot path."""
path_str = str(path)
assert path_str.startswith("/tmp"), path
# Remove the leading `/` from the output file so it joins properly.
return repo_root / info.out_dir_name / str(path)[1:]
def determine_upload_command(profile_path: Path, rev: int) -> pgo_utils.Command:
"""Returns a command that can be used to upload our PGO profile."""
upload_target = (
f"gs://chromeos-localmirror/distfiles/llvm-profdata-r{rev}.xz"
)
return [
"gsutil.py",
"cp",
"-n",
"-a",
"public-read",
profile_path,
upload_target,
]
def main(argv: List[str]):
logging.basicConfig(
format=">> %(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: "
"%(message)s",
level=logging.INFO,
)
my_dir = Path(__file__).resolve().parent
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--chroot",
default="llvm-next-pgo-chroot",
help="""
Name of the chroot to create. Will be recreated if it exists already.
""",
)
parser.add_argument(
"--clean-llvm",
action="store_true",
help="Allow the overwriting of any local changes to LLVM.",
)
parser.add_argument(
"--rev",
type=int,
help="Revision of LLVM to generate a PGO profile for.",
)
parser.add_argument(
"--out-dir",
default="llvm-next-pgo-chroot_out",
help="""
Name of the out/ directory to use. Will be recreated if it exists
already.
""",
)
parser.add_argument(
"--upload",
action="store_true",
help="Upload the profile after creation. Implies --compress.",
)
parser.add_argument(
"--output",
type=Path,
help="""
Additionally put the uncompressed profile at the this path after
creation.
""",
)
opts = parser.parse_args(argv)
pgo_utils.exit_if_in_chroot()
rev = opts.rev
# This translation can take a few seconds, so give a helpful log. If
# anything needs to be fetched, `get_llvm_hash` will also print a "this may
# take a while"-style message.
logging.info("Translating r%d to a git SHA", rev)
sha = get_llvm_hash.GetGitHashFrom(
get_llvm_hash.GetAndUpdateLLVMProjectInLLVMTools(),
rev,
)
logging.info("r%d == %s", rev, sha)
repo_root = chroot.FindChromeOSRootAbove(my_dir)
logging.info("Repo root is %s", repo_root)
logging.info("Creating new SDK")
bootstrap_sdk_version = detect_bootstrap_sdk_version(repo_root)
logging.info("Detected bootstrap SDK version: %s", bootstrap_sdk_version)
bootstrap_chroot_info = ChrootInfo(
opts.chroot, opts.out_dir, bootstrap_sdk_version
)
try:
create_fresh_chroot(repo_root, bootstrap_chroot_info)
chroot_profile_path = Path("/tmp/llvm-next-pgo-profile.prof")
generate_pgo_profile(
repo_root=repo_root,
chroot_info=bootstrap_chroot_info,
chroot_output_file=chroot_profile_path,
sha=sha,
clean_llvm=opts.clean_llvm,
)
profile_path = translate_chroot_path_to_out_of_chroot(
repo_root, chroot_profile_path, bootstrap_chroot_info
)
if opts.output:
shutil.copyfile(profile_path, opts.output)
compressed_profile_path = compress_pgo_profile(profile_path)
upload_command = determine_upload_command(compressed_profile_path, rev)
if opts.upload:
pgo_utils.run(upload_command)
else:
friendly_upload_command = shlex.join(str(x) for x in upload_command)
logging.info(
"To upload the profile, run %r in %r",
friendly_upload_command,
repo_root,
)
except:
logging.warning(
"NOTE: Chroot left at %s and out dir is left at %s. "
"If you don't plan to rerun this script, delete them.",
bootstrap_chroot_info.chroot_name,
bootstrap_chroot_info.out_dir_name,
)
raise
else:
logging.info(
"Feel free to delete chroot %s and out dir %s when you're done "
"with them.",
bootstrap_chroot_info.chroot_name,
bootstrap_chroot_info.out_dir_name,
)