Skip to content

Commit

Permalink
llvm: build with PGO
Browse files Browse the repository at this point in the history
This builds LLVM with profile-guided optimisations. I've adapted the
build from upstream documentation. [1, 2]

This does mean that builds will now take 2-3x longer than before. LLVM
currently takes about four hours to build at most (including recursive
dependents), so the additional time isn't as bad as some other formulae
that require long CI runs. Moreover, the collective time this saves
users of the LLVM formula should make the additional build time worth
it.

LTO is another potential optimisation that I haven't enabled here.
This appears to be enabled in Apple's default build [3], but is a little
complicated to implement for an LLVM distribution that includes static
archives [4].

Closes Homebrew#77975

[1] https://llvm.org/docs/HowToBuildWithPGO.html#building-clang-with-pgo
[2] https://github.com/llvm/llvm-project/blob/33ba8bd2/llvm/utils/collect_and_build_with_pgo.py
[3] https://github.com/apple/llvm-project/blob/swift-5.4-RELEASE/clang/cmake/caches/Apple-stage2.cmake#L30
[4] https://llvm.org/docs/BuildingADistribution.html#options-for-optimizing-llvm
  • Loading branch information
carlocab committed Jul 4, 2021
1 parent 66502a2 commit 2166e91
Showing 1 changed file with 133 additions and 5 deletions.
138 changes: 133 additions & 5 deletions Formula/llvm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,17 @@ def install
clang-tools-extra
lld
lldb
openmp
polly
mlir
polly
]
runtimes = %w[
compiler-rt
libcxx
libcxxabi
libunwind
]
on_macos { runtimes << "openmp" }
on_linux { projects << "openmp" }

py_ver = Language::Python.major_minor_version("python3")
site_packages = Language::Python.site_packages("python3").delete_prefix("lib/")
Expand Down Expand Up @@ -110,7 +111,7 @@ def install
-DLIBOMP_INSTALL_ALIASES=OFF
-DCLANG_PYTHON_BINDINGS_VERSIONS=#{py_ver}
-DPACKAGE_VENDOR=#{tap.user}
-DPACKAGE_BUGREPORT=#{tap.issues_url}
-DBUG_REPORT_URL=#{tap.issues_url}
-DCLANG_VENDOR_UTI=org.#{tap.user.downcase}.clang
]

Expand All @@ -122,13 +123,12 @@ def install
args << "-DFFI_LIBRARY_DIR=#{Formula["libffi"].opt_lib}"
end

sdk = MacOS.sdk_path_if_needed
on_macos do
args << "-DLLVM_BUILD_LLVM_C_DYLIB=ON"
args << "-DLLVM_ENABLE_LIBCXX=ON"
args << "-DLLVM_CREATE_XCODE_TOOLCHAIN=#{MacOS::Xcode.installed? ? "ON" : "OFF"}"
args << "-DRUNTIMES_CMAKE_ARGS=-DCMAKE_INSTALL_RPATH=#{rpath}"

sdk = MacOS.sdk_path_if_needed
args << "-DDEFAULT_SYSROOT=#{sdk}" if sdk
end

Expand Down Expand Up @@ -163,6 +163,134 @@ def install
end

llvmpath = buildpath/"llvm"
pgo_build = false
on_macos { pgo_build = build.stable? && build.bottle? }
if pgo_build
# We build LLVM a few times first for optimisations. See
# https://github.com/Homebrew/homebrew-core/issues/77975

# PGO build adapted from:
# https://llvm.org/docs/HowToBuildWithPGO.html#building-clang-with-pgo
# https://github.com/llvm/llvm-project/blob/33ba8bd2/llvm/utils/collect_and_build_with_pgo.py
# https://github.com/facebookincubator/BOLT/blob/01f471e7/docs/OptimizingClang.md
extra_args = [
"-DLLVM_TARGETS_TO_BUILD=Native",
"-DLLVM_ENABLE_PROJECTS=clang;compiler-rt;lld",
]
cflags = ENV.cflags&.split || []
cxxflags = ENV.cxxflags&.split || []

# The later stage builds avoid the shims, and the build
# will target Penryn unless otherwise specified
if Hardware::CPU.intel?
cflags << "-march=#{Hardware.oldest_cpu}"
cxxflags << "-march=#{Hardware.oldest_cpu}"
end

on_macos do
extra_args << "-DLLVM_ENABLE_LIBCXX=ON"
extra_args << "-DDEFAULT_SYSROOT=#{sdk}" if sdk
end

extra_args << "-DCMAKE_C_FLAGS=#{cflags.join(" ")}" unless cflags.empty?
extra_args << "-DCMAKE_CXX_FLAGS=#{cxxflags.join(" ")}" unless cxxflags.empty?

# First, build a stage1 compiler. It might be possible to skip this step on macOS
# and use system Clang instead, but this stage does not take too long, and we want
# to avoid incompatibilities from generating profile data with a newer Clang than
# the one we consume the data with.
mkdir llvmpath/"stage1" do
system "cmake", "-G", "Unix Makefiles", "..",
*extra_args, *std_cmake_args
system "cmake", "--build", ".", "--target", "clang", "llvm-profdata", "profile"
end

# Our just-built Clang needs a little help finding C++ headers,
# since the atomic and type_traits headers are not in the SDK
# on macOS versions before Big Sur.
on_macos do
if MacOS.version <= :catalina && sdk
toolchain_path = if MacOS::CLT.installed?
MacOS::CLT::PKG_PATH
else
MacOS::Xcode.toolchain_path
end

cxxflags << "-isystem#{toolchain_path}/usr/include/c++/v1"
cxxflags << "-isystem#{toolchain_path}/usr/include"
cxxflags << "-isystem#{MacOS.sdk_path_if_needed}/usr/include"

extra_args.reject! { |s| s["CMAKE_CXX_FLAGS"] }
extra_args << "-DCMAKE_CXX_FLAGS=#{cxxflags.join(" ")}"
end
end

# Next, build an instrumented stage2 compiler
mkdir llvmpath/"stage2" do
# LLVM Profile runs out of static counters
# https://reviews.llvm.org/D92669, https://reviews.llvm.org/D93281
# Without this, the build produces many warnings of the form
# LLVM Profile Warning: Unable to track new values: Running out of static counters.
instrumented_cflags = cflags + ["-Xclang -mllvm -Xclang -vp-counters-per-site=6"]
instrumented_cxxflags = cxxflags + ["-Xclang -mllvm -Xclang -vp-counters-per-site=6"]
instrumented_extra_args = extra_args.reject { |s| s["CMAKE_C_FLAGS"] || s["CMAKE_CXX_FLAGS"] }

system "cmake", "-G", "Unix Makefiles", "..",
"-DCMAKE_C_COMPILER=#{llvmpath}/stage1/bin/clang",
"-DCMAKE_CXX_COMPILER=#{llvmpath}/stage1/bin/clang++",
"-DLLVM_BUILD_INSTRUMENTED=IR",
"-DLLVM_BUILD_RUNTIME=NO",
"-DCMAKE_C_FLAGS=#{instrumented_cflags.join(" ")}",
"-DCMAKE_CXX_FLAGS=#{instrumented_cxxflags.join(" ")}",
*instrumented_extra_args, *std_cmake_args
system "cmake", "--build", ".", "--target", "clang", "lld"

# We run some `check-*` targets to increase profiling
# coverage. These do not need to succeed.
begin
system "cmake", "--build", ".", "--target", "check-clang", "check-llvm", "--", "--keep-going"
rescue RuntimeError
nil
end
end

# Then, generate the profile data
mkdir llvmpath/"stage2-profdata" do
system "cmake", "-G", "Unix Makefiles", "..",
"-DCMAKE_C_COMPILER=#{llvmpath}/stage2/bin/clang",
"-DCMAKE_CXX_COMPILER=#{llvmpath}/stage2/bin/clang++",
*extra_args, *std_cmake_args

# This build is for profiling, so it is safe to ignore errors.
# We pass `--keep-going` to `make` to ignore the error that requires
# deparallelisation on ARM. (See below.)
begin
system "cmake", "--build", ".", "--", "--keep-going"
rescue RuntimeError
nil
end
end

# Merge the generated profile data
profpath = llvmpath/"stage2/profiles"
system llvmpath/"stage1/bin/llvm-profdata",
"merge",
"-output=#{profpath}/pgo_profile.prof",
*Dir[profpath/"*.profraw"]

# Make sure to build with our profiled compiler and use the profile data
args << "-DCMAKE_C_COMPILER=#{llvmpath}/stage1/bin/clang"
args << "-DCMAKE_CXX_COMPILER=#{llvmpath}/stage1/bin/clang++"
args << "-DLLVM_PROFDATA_FILE=#{profpath}/pgo_profile.prof"

# Silence some warnings
cflags << "-Wno-backend-plugin"
cxxflags << "-Wno-backend-plugin"
args << "-DCMAKE_C_FLAGS=#{cflags.join(" ")}"
args << "-DCMAKE_CXX_FLAGS=#{cxxflags.join(" ")}"
end

# Now, we can build.
mkdir llvmpath/"build" do
system "cmake", "-G", "Unix Makefiles", "..", *(std_cmake_args + args)
# Workaround for CMake Error: failed to create symbolic link
Expand Down

0 comments on commit 2166e91

Please sign in to comment.