blob: b666af77dd20bbc903921141a9724bdaa66c5681 [file] [log] [blame]
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""FDO context describes how C++ FDO compilation should be done."""
load(":common/cc/fdo/fdo_prefetch_hints.bzl", "FdoPrefetchHintsInfo")
load(":common/cc/fdo/fdo_profile.bzl", "FdoProfileInfo")
load(":common/cc/fdo/memprof_profile.bzl", "MemProfProfileInfo")
load(":common/cc/fdo/propeller_optimize.bzl", "PropellerOptimizeInfo")
load(":common/paths.bzl", "paths")
cc_internal = _builtins.internal.cc_internal
def _create_fdo_context(
ctx,
*,
llvm_profdata,
all_files,
zipper,
cc_toolchain_config_info,
coverage_enabled,
_fdo_prefetch_hints,
_propeller_optimize,
_memprof_profile,
_fdo_optimize,
_fdo_profile,
_xfdo_profile,
_csfdo_profile,
_proto_profile):
"""Creates FDO context when it should be available.
When `-c opt` is used it parses values of FDO related flags, processes the
input Files and return Files ready to be used for FDO.
Args:
ctx: (SubruleContext) used to create actions and obtain configuration
llvm_profdata: (File) llvm-profdata executable
all_files: (depset(File)) Files needed to run llvm-profdata
zipper: (File) zip tool, used to unpact the profiles
cc_toolchain_config_info: (CcToolchainConfigInfo) Used to check CPU value, should be removed
coverage_enabled: (bool) Is code coverage enabled
_fdo_prefetch_hints: (Target) Pointed to by --fdo_prefetch_hints
_propeller_optimize: (Target) Pointed to by --propeller_optimize
_memprof_profile: (Target) Pointed to by --memprof_profile
_fdo_optimize: (Target) Pointed to by --fdo_optimize
_fdo_profile: (Target) Pointed to by --fdo_profile
_xfdo_profile: (Target) Pointed to by --xbinary_fdo
_csfdo_profile: (Target) Pointed to by --cs_fdo_profile
_proto_profile: (Target) Pointed to by --proto_profile_path
Returns:
(FDOContext) A structure with following fields:
- branch_fdo_profile (struct|None)
- branch_fdo_mode ("auto_fdo"|"xbinary_fdo"|"llvm_fdo"|llvm_cs_fdo")
- profile_artifact (File)
- proto_profile_artifact (File)
- prefetch_hints_artifact (File),
- propeller_optimize_info (PropellerOptimizeInfo)
- memprof_profile_artifact (File)
"""
cpp_config = ctx.fragments.cpp
if cpp_config.compilation_mode() != "opt":
return struct()
# Propeller optimize cc and ld profiles
cc_profile = _symlink_to(
ctx,
name_prefix = "fdo",
absolute_path = cpp_config.propeller_optimize_absolute_cc_profile(),
progress_message = "Symlinking LLVM Propeller Profile %{input}",
)
ld_profile = _symlink_to(
ctx,
name_prefix = "fdo",
absolute_path = cpp_config.propeller_optimize_absolute_ld_profile(),
progress_message = "Symlinking LLVM Propeller Profile %{input}",
)
if cc_profile or ld_profile:
propeller_optimize_info = PropellerOptimizeInfo(
cc_profile = cc_profile,
ld_profile = ld_profile,
)
elif _propeller_optimize:
propeller_optimize_info = _propeller_optimize[PropellerOptimizeInfo]
else:
propeller_optimize_info = None
# Attempt to fetch the memprof profile input from an explicit flag or as part of the
# fdo_profile rule. The former overrides the latter. Also handle the case where the
# fdo_profile rule is specified using fdo_optimize.
mem_prof_profile = None
if _memprof_profile:
mem_prof_profile = _memprof_profile[MemProfProfileInfo]
elif _fdo_profile and _fdo_profile[FdoProfileInfo].memprof_artifact:
mem_prof_profile = MemProfProfileInfo(artifact = _fdo_profile[FdoProfileInfo].memprof_artifact)
elif _fdo_optimize and FdoProfileInfo in _fdo_optimize and _fdo_optimize[FdoProfileInfo].memprof_artifact:
mem_prof_profile = MemProfProfileInfo(artifact = _fdo_optimize[FdoProfileInfo].memprof_artifact)
fdo_inputs = None
if cpp_config.fdo_path():
# TODO(b/333997009): computation of cpp_config.fdo_path in CppConfiguration class is convoluted, simplify it
# fdoZip should be set if the profile is a path, fdoInputFile if it is an artifact, but never both
fdo_inputs = FdoProfileInfo(absolute_path = cpp_config.fdo_path())
elif _fdo_optimize:
if FdoProfileInfo in _fdo_optimize:
fdo_inputs = _fdo_optimize[FdoProfileInfo]
elif _fdo_optimize[DefaultInfo].files:
if len(_fdo_optimize[DefaultInfo].files.to_list()) != 1:
fail("--fdo_optimize does not point to a single target")
[fdo_optimize_artifact] = _fdo_optimize[DefaultInfo].files.to_list()
if fdo_optimize_artifact.short_path != _fdo_optimize.label.package + "/" + _fdo_optimize.label.name:
fail("--fdo_optimize points to a target that is not an input file or an fdo_profile rule")
fdo_inputs = FdoProfileInfo(artifact = fdo_optimize_artifact)
elif _fdo_profile:
fdo_inputs = _fdo_profile[FdoProfileInfo]
elif _xfdo_profile:
fdo_inputs = _xfdo_profile[FdoProfileInfo]
cs_fdo_input = None
if cpp_config.cs_fdo_path():
cs_fdo_input = FdoProfileInfo(absolute_path = cpp_config.cs_fdo_path())
elif _csfdo_profile:
cs_fdo_input = _csfdo_profile[FdoProfileInfo]
# If --noproto_profile is in effect, there is no proto profile.
# If --proto_profile_path=<label> is passed, that profile is used.
# If AutoFDO is not in effect, no profile is used.
# If AutoFDO is in effect, a file called proto.profile next to the AutoFDO
# profile is used, if it exists.
proto_profile_artifact = None
if not cpp_config.proto_profile() and _proto_profile:
fail("--proto_profile_path cannot be set if --proto_profile is false")
if _proto_profile:
proto_profile_artifact = _symlink_to(
ctx,
name_prefix = "fdo",
artifact = _proto_profile,
progress_message = "Symlinking protobuf profile %{input}",
)
elif cpp_config.proto_profile():
proto_profile_artifact = getattr(fdo_inputs, "proto_profile_artifact", None)
branch_fdo_profile = None
if fdo_inputs:
branch_fdo_modes = {
".afdo": "auto_fdo",
".xfdo": "xbinary_fdo",
".profdata": "llvm_fdo",
".profraw": "llvm_fdo",
".zip": "llvm_fdo",
}
extension = paths.split_extension(_basename(fdo_inputs))[1]
if extension not in branch_fdo_modes:
fail("invalid extension for FDO profile file")
branch_fdo_mode = branch_fdo_modes[extension]
if branch_fdo_mode == "llvm_fdo":
# Check if this is LLVM_CS_FDO
if cs_fdo_input:
branch_fdo_mode = "llvm_cs_fdo"
if _xfdo_profile:
fail("--xbinary_fdo only accepts *.xfdo and *.afdo")
if coverage_enabled:
fail("coverage mode is not compatible with FDO optimization")
# This tries to convert LLVM profiles to the indexed format if necessary.
if branch_fdo_mode == "llvm_fdo":
profile_artifact = _convert_llvm_raw_profile_to_indexed(
ctx,
"fdo",
fdo_inputs,
llvm_profdata,
all_files,
zipper,
cc_toolchain_config_info,
)
elif branch_fdo_mode in ["auto_fdo", "xbinary_fdo"]:
profile_artifact = _symlink_input(
ctx,
"fdo",
fdo_inputs,
"Symlinking FDO profile %{input}",
)
else: # branch_fdo_mode == "llvm_cs_fdo":
non_cs_profile_artifact = _convert_llvm_raw_profile_to_indexed(
ctx,
"fdo",
fdo_inputs,
llvm_profdata,
all_files,
zipper,
cc_toolchain_config_info,
)
cs_profile_artifact = _convert_llvm_raw_profile_to_indexed(
ctx,
"csfdo",
cs_fdo_input,
llvm_profdata,
all_files,
zipper,
cc_toolchain_config_info,
)
profile_artifact = _merge_llvm_profiles(
ctx,
"mergedfdo",
llvm_profdata,
all_files,
non_cs_profile_artifact,
cs_profile_artifact,
"MergedCS.profdata",
)
branch_fdo_profile = struct(
branch_fdo_mode = branch_fdo_mode,
profile_artifact = profile_artifact,
)
prefetch_hints_artifact = None
if _fdo_prefetch_hints:
prefetch_hints_artifact = _symlink_input(
ctx,
"fdo",
_fdo_prefetch_hints[FdoPrefetchHintsInfo],
"Symlinking LLVM Cache Prefetch Hints Profile %{input}",
)
memprof_profile_artifact = _get_mem_prof_profile_artifact(zipper, mem_prof_profile, ctx)
return struct(
branch_fdo_profile = branch_fdo_profile,
prefetch_hints_artifact = prefetch_hints_artifact,
propeller_optimize_info = propeller_optimize_info,
memprof_profile_artifact = memprof_profile_artifact,
proto_profile_artifact = proto_profile_artifact,
)
def _convert_llvm_raw_profile_to_indexed(
ctx,
name_prefix,
fdo_inputs,
llvm_profdata,
all_files,
zipper,
cc_toolchain_config_info):
"""This function checks the input profile format and converts it to the indexed format (.profdata) if necessary."""
basename = _basename(fdo_inputs)
if basename.endswith(".profdata"):
return _symlink_input(ctx, name_prefix, fdo_inputs, "Symlinking LLVM Profile %{input}")
if basename.endswith(".zip"):
if not zipper:
fail("Zipped profiles are not supported with platforms/toolchains before toolchain-transitions are implemented.")
zip_profile_artifact = _symlink_input(ctx, name_prefix, fdo_inputs, "Symlinking LLVM ZIP Profile %{input}")
# TODO(b/333997009): find a way to avoid hard-coding cpu architecture here
cpu = cc_toolchain_config_info.target_cpu()
if "k8" == cpu:
raw_profile_file_name = name_prefix + "/" + ctx.label.name + "/" + "fdocontrolz_profile.profraw"
else:
raw_profile_file_name = name_prefix + "/" + ctx.label.name + "/" + "fdocontrolz_profile-" + cpu + ".profraw"
raw_profile_artifact = ctx.actions.declare_file(raw_profile_file_name)
# We invoke different binaries depending on whether the unzip_fdo tool
# is available. When it isn't, unzip_fdo is aliased to the generic
# zipper tool, which takes different command-line arguments.
args = ctx.actions.args()
if zipper.path.endswith("unzip_fdo"):
args.add("--profile_zip", zip_profile_artifact)
args.add("--cpu", cpu)
args.add("--output_file", raw_profile_artifact)
else:
args.add("xf", zip_profile_artifact)
args.add("-d", raw_profile_artifact.dirname)
ctx.actions.run(
mnemonic = "LLVMUnzipProfileAction",
executable = zipper,
arguments = [args],
inputs = [zip_profile_artifact],
outputs = [raw_profile_artifact],
progress_message = "LLVMUnzipProfileAction: Generating %{output}",
)
else: # .profraw
raw_profile_artifact = _symlink_input(ctx, name_prefix, fdo_inputs, "Symlinking LLVM Raw Profile %{input}")
if not llvm_profdata:
fail("llvm-profdata not available with this crosstool, needed for profile conversion")
name = name_prefix + "/" + ctx.label.name + "/" + paths.replace_extension(basename, ".profdata")
profile_artifact = ctx.actions.declare_file(name)
ctx.actions.run(
mnemonic = "LLVMProfDataAction",
executable = llvm_profdata,
tools = [all_files],
arguments = [ctx.actions.args().add("merge").add("-o").add(profile_artifact).add(raw_profile_artifact)],
inputs = [raw_profile_artifact],
outputs = [profile_artifact],
use_default_shell_env = True,
progress_message = "LLVMProfDataAction: Generating %{output}",
)
return profile_artifact
def _merge_llvm_profiles(
ctx,
name_prefix,
llvm_profdata,
all_files,
profile1,
profile2,
merged_output_name):
"""This function merges profile1 and profile2 and generates merged_output."""
profile_artifact = ctx.actions.declare_file(name_prefix + "/" + ctx.label.name + "/" + merged_output_name)
# Merge LLVM profiles.
ctx.actions.run(
mnemonic = "LLVMProfDataMergeAction",
executable = llvm_profdata,
tools = [all_files],
arguments = [ctx.actions.args().add("merge").add("-o").add(profile_artifact).add(profile1).add(profile2)],
inputs = [profile1, profile2],
outputs = [profile_artifact],
use_default_shell_env = True,
progress_message = "LLVMProfDataAction: Generating %{output}",
)
return profile_artifact
def _basename(inputs):
if getattr(inputs, "artifact", None):
return inputs.artifact.basename
else:
return paths.basename(inputs.absolute_path)
def _symlink_input(ctx, name_prefix, fdo_inputs, progress_message, basename = None):
return _symlink_to(
ctx,
name_prefix,
progress_message,
getattr(fdo_inputs, "artifact", None),
getattr(fdo_inputs, "absolute_path", None),
basename,
)
def _symlink_to(ctx, name_prefix, progress_message, artifact = None, absolute_path = None, basename = None):
"""Symlinks either an absolute path or file to a unique location."""
if artifact:
if not basename:
basename = artifact.basename
name = name_prefix + "/" + ctx.label.name + "/" + basename
output = ctx.actions.declare_file(name)
ctx.actions.symlink(
output = output,
target_file = artifact,
progress_message = progress_message,
)
return output
elif absolute_path:
if not basename:
basename = paths.basename(absolute_path)
name = name_prefix + "/" + ctx.label.name + "/" + basename
output = ctx.actions.declare_file(name)
cc_internal.absolute_symlink(
ctx = ctx,
output = output,
target_path = absolute_path,
progress_message = progress_message,
)
return output
else:
return None
def _get_mem_prof_profile_artifact(zipper, memprof_profile, ctx):
"""This function symlinks the memprof profile (after unzipping as needed)."""
if not memprof_profile:
return None
basename = _basename(memprof_profile)
# If the profile file is already in the desired format, symlink to it and return.
if basename.endswith(".profdata"):
return _symlink_input(ctx, "memprof", memprof_profile, "Symlinking MemProf Profile %{input}", basename = "memprof.profdata")
if not basename.endswith(".zip"):
fail("Expected zipped memprof profile.")
if not zipper:
fail("Zipped profiles are not supported with platforms/toolchains before " +
"toolchain-transitions are implemented.")
zip_profile_artifact = _symlink_input(ctx, "memprof", memprof_profile, "Symlinking MemProf ZIP Profile %{input}")
profile_artifact = ctx.actions.declare_file("memprof/" + ctx.label.name + "/memprof.profdata")
# We invoke different binaries depending on whether the unzip_fdo tool
# is available. When it isn't, unzip_fdo is aliased to the generic
# zipper tool, which takes different command-line arguments.
args = ctx.actions.args()
if zipper.path.endswith("unzip_fdo"):
args.add("--profile_zip", zip_profile_artifact)
args.add("--memprof")
args.add("--output_file", profile_artifact)
else:
args.add("xf", zip_profile_artifact)
args.add("-d", profile_artifact.dirname)
ctx.actions.run(
mnemonic = "LLVMUnzipProfileAction",
executable = zipper,
arguments = [args],
inputs = [zip_profile_artifact],
outputs = [profile_artifact],
progress_message = "MemProfUnzipProfileAction: Generating %{output}",
)
return profile_artifact
create_fdo_context = subrule(
implementation = _create_fdo_context,
fragments = ["cpp"],
attrs = {
"_fdo_optimize": attr.label(
default = configuration_field(fragment = "cpp", name = "fdo_optimize"),
allow_files = True,
providers = [[DefaultInfo], [FdoProfileInfo]],
),
"_xfdo_profile": attr.label(
default = configuration_field(fragment = "cpp", name = "xbinary_fdo"),
providers = [FdoProfileInfo],
),
"_fdo_profile": attr.label(
default = configuration_field(fragment = "cpp", name = "fdo_profile"),
providers = [FdoProfileInfo],
),
"_csfdo_profile": attr.label(
default = configuration_field(fragment = "cpp", name = "cs_fdo_profile"),
providers = [FdoProfileInfo],
),
"_fdo_prefetch_hints": attr.label(
default = configuration_field(fragment = "cpp", name = "fdo_prefetch_hints"),
providers = [FdoPrefetchHintsInfo],
),
"_propeller_optimize": attr.label(
default = configuration_field(fragment = "cpp", name = "propeller_optimize"),
providers = [PropellerOptimizeInfo],
),
"_memprof_profile": attr.label(
default = configuration_field(fragment = "cpp", name = "memprof_profile"),
providers = [MemProfProfileInfo],
),
"_proto_profile": attr.label(
default = configuration_field(fragment = "cpp", name = "proto_profile_path"),
allow_single_file = True,
),
},
)