gecko-dev/build/moz.configure/lto-pgo.configure
Nathan Froyd 3608ab49b8 Bug 1572216 - move LTO defaulting into mozconfig.win-common; r=glandium
Some recent changes to how we set cross-language LTO for Windows
resulted in compilation-time decreases and small performance regressions
on a few benchmarks.  The changes intended to remove explicit enablement
of cross-language LTO for all builds, but rely on shippable builds being
built with PGO and moz.configure's clever defaulting of cross-language
LTO for PGO'd builds on Windows, which would then enable cross-language
LTO for only shippable builds.

Obviously something went wrong with those changes.

The problem was our defaulting wasn't visible to moz.configure's logic
for how to pass command-line options to the JS subconfigure.  We set the
value (`cross`) after the value for `--enable-lto` has been determined,
and the default value is off (that is, `--disable-lto`).  Since
moz.configure is very thorough in passing configure options down into
JS, it dutifully looked at what the default value of `--enable-lto` was
supposed to be, and passed `--disable-lto` to JS's configure.

There's some evidence that we knew our defaulting was a little sketchy:
we'd only attempt cross-language LTO when we were performing the PGO use
phase, and only if the value of `--enable-lto` wasn't explicitly passed.
Which was a fine idea--you don't want to override what the user was
trying to do--but in the case of JS backfired on us: the value *was*
coming from the explicitly-passed command-line option of
`--disable-lto`.  So JS didn't enable any kind of LTO, with attendant
consequences.

This problem *didn't* happen before the aforementioned change because we
were explicitly specifying that `--enable-lto=cross` should be passed in
the mozconfig, which ensured that the correct setting was passed into
JS.  We were just setting `--enable-lto=cross` for *all* builds, which
was less than desirable.

The easiest way to fix all this is simply to put the
`--enable-lto=cross` setting in the Windows mozconfigs, conditional on
`MOZ_PGO_PROFILE_USE`.  That placement captures the intent of the
previous attempt at defaulting, but without the troubles described
above: the option explicitly appears on the command line, and
moz.configure will correctly pass it through to the JS subconfigure.
This also makes our Windows configuration closer to our Linux
configuration (the Linux configuration enables cross-language LTO for
both PGO phases, which is arguably a bug).

Differential Revision: https://phabricator.services.mozilla.com/D41080

--HG--
extra : moz-landing-system : lando
2019-08-09 13:22:08 +00:00

254 lines
9.2 KiB
Python

# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# PGO
# ==============================================================
@depends(c_compiler, check_build_environment, target)
@imports('multiprocessing')
@imports(_from='__builtin__', _import='min')
def pgo_flags(compiler, build_env, target):
topobjdir = build_env.topobjdir
if topobjdir.endswith('/js/src'):
topobjdir = topobjdir[:-7]
if compiler.type == 'gcc':
return namespace(
gen_cflags=['-fprofile-generate'],
gen_ldflags=['-fprofile-generate'],
use_cflags=['-fprofile-use', '-fprofile-correction',
'-Wcoverage-mismatch'],
use_ldflags=['-fprofile-use'],
)
if compiler.type in ('clang-cl', 'clang'):
profdata = os.path.join(topobjdir, 'merged.profdata')
prefix = ''
if compiler.type == 'clang-cl':
prefix = '/clang:'
if target.cpu == 'x86_64':
gen_ldflags = ['clang_rt.profile-x86_64.lib']
elif target.cpu == 'x86':
gen_ldflags = ['clang_rt.profile-i386.lib']
else:
gen_ldflags = None
else:
gen_ldflags = ['-fprofile-generate']
return namespace(
gen_cflags=[prefix + '-fprofile-generate'],
gen_ldflags=gen_ldflags,
use_cflags=[prefix + '-fprofile-use=%s' % profdata,
# Some error messages about mismatched profile data
# come in via -Wbackend-plugin, so disable those too.
'-Wno-error=backend-plugin'],
use_ldflags=[],
)
set_config('PROFILE_GEN_CFLAGS', pgo_flags.gen_cflags)
set_config('PROFILE_GEN_LDFLAGS', pgo_flags.gen_ldflags)
set_config('PROFILE_USE_CFLAGS', pgo_flags.use_cflags)
set_config('PROFILE_USE_LDFLAGS', pgo_flags.use_ldflags)
llvm_profdata = check_prog('LLVM_PROFDATA', ['llvm-profdata'],
allow_missing=True,
paths=toolchain_search_path)
js_option('--enable-profile-generate',
nargs='?',
choices=('cross',),
help='Build a PGO instrumented binary')
imply_option('MOZ_PGO',
depends_if('--enable-profile-generate')(lambda _: True))
set_config('MOZ_PROFILE_GENERATE',
depends_if('--enable-profile-generate')(lambda _: True))
set_define('MOZ_PROFILE_GENERATE',
depends_if('--enable-profile-generate')(lambda _: True))
js_option('--enable-profile-use',
nargs='?',
choices=('cross',),
help='Use a generated profile during the build')
js_option('--with-pgo-profile-path',
help='Path to the directory with unmerged profile data to use during the build',
nargs=1)
js_option('--enable-cross-pgo',
help='Enable PGO on Rust code')
imply_option('MOZ_PGO',
depends_if('--enable-profile-use')(lambda _: True))
set_config('MOZ_PROFILE_USE',
depends_if('--enable-profile-use')(lambda _: True))
@depends('--with-pgo-profile-path', '--enable-profile-use', llvm_profdata)
@imports('os')
def pgo_profile_path(path, pgo_use, profdata):
if not path:
return
if path and not pgo_use:
die('Pass --enable-profile-use to use --with-pgo-profile-path.')
if path and not profdata:
die('LLVM_PROFDATA must be set to process the pgo profile.')
if not os.path.isdir(path[0]):
die('Argument to --with-pgo-profile-path must be a directory.')
if not os.path.isabs(path[0]):
die('Argument to --with-pgo-profile-path must be an absolute path.')
return path[0]
set_config('PGO_PROFILE_PATH', pgo_profile_path)
option('--with-pgo-jarlog',
help='Use the provided jarlog file when packaging during a profile-use '
'build',
nargs=1)
set_config('PGO_JARLOG_PATH', depends_if('--with-pgo-jarlog')(lambda p: p))
@depends('MOZ_PGO', '--enable-profile-use', '--enable-profile-generate',
c_compiler, rustc_info)
def moz_pgo_rust(pgo, profile_use, profile_generate, c_compiler, rustc):
if not pgo:
return
# Enabling PGO through MOZ_PGO only and not --enable* flags.
if not profile_use and not profile_generate:
return
if profile_use and profile_generate:
die('Cannot build with --enable-profile-use and --enable-profile-generate.')
want_cross = (len(profile_use) and profile_use[0] == 'cross') \
or (len(profile_generate) and profile_generate[0] == 'cross')
if not want_cross:
return
if c_compiler.type == 'gcc':
die('Cannot use cross-language PGO with GCC.')
# PGO is not stable prior to 1.37
if rustc.version < Version('1.37'):
die('Cannot use cross-language PGO with Rust version %s.' % rustc.version)
return True
set_config('MOZ_PGO_RUST', moz_pgo_rust)
# LTO
# ==============================================================
js_option('--enable-lto',
env='MOZ_LTO',
nargs='?',
choices=('full', 'thin', 'cross'),
help='Enable LTO')
js_option(env='MOZ_LD64_KNOWN_GOOD',
nargs=1,
help='Indicate that ld64 is free of symbol aliasing bugs.')
imply_option('MOZ_LD64_KNOWN_GOOD', depends_if('MOZ_AUTOMATION')(lambda _: True))
@depends('--enable-lto', c_compiler, 'MOZ_LD64_KNOWN_GOOD', target)
@imports('multiprocessing')
def lto(value, c_compiler, ld64_known_good, target):
cflags = []
ldflags = []
enabled = None
rust_lto = False
if value:
enabled = True
# `cross` implies `thin`, but with Rust code participating in LTO
# as well. Make that a little more explicit.
if len(value) and value[0].lower() == 'cross':
if c_compiler.type == 'gcc':
die('Cross-language LTO is not supported with GCC.')
rust_lto = True
value = ['thin']
if target.kernel == 'Darwin' and target.os == 'OSX' \
and value[0].lower() == 'cross' and not ld64_known_good:
die('The Mac linker is known to have a bug that affects cross-language '
'LTO. If you know that your linker is free from this bug, please '
'set the environment variable `MOZ_LD64_KNOWN_GOOD=1` and re-run '
'configure.')
if c_compiler.type == 'clang':
if len(value) and value[0].lower() == 'full':
cflags.append("-flto")
ldflags.append("-flto")
else:
cflags.append("-flto=thin")
ldflags.append("-flto=thin")
elif c_compiler.type == 'clang-cl':
if len(value) and value[0].lower() == 'full':
cflags.append("-flto")
else:
cflags.append("-flto=thin")
# With clang-cl, -flto can only be used with -c or -fuse-ld=lld.
# AC_TRY_LINKs during configure don't have -c, so pass -fuse-ld=lld.
cflags.append("-fuse-ld=lld");
# Explicitly set the CPU to optimize for so the linker doesn't
# choose a poor default. Rust compilation by default uses the
# pentium4 CPU on x86:
#
# https://github.com/rust-lang/rust/blob/master/src/librustc_target/spec/i686_pc_windows_msvc.rs#L5
#
# which specifically supports "long" (multi-byte) nops. See
# https://bugzilla.mozilla.org/show_bug.cgi?id=1568450#c8 for details.
#
# The pentium4 seems like kind of a weird CPU to optimize for, but
# it seems to have worked out OK thus far. LLVM does not seem to
# specifically schedule code for the pentium4's deep pipeline, so
# that probably contributes to it being an OK default for our
# purposes.
if target.cpu == 'x86':
ldflags.append('-mllvm:-mcpu=pentium4')
# This is also the CPU that Rust uses. The LLVM source code
# recommends this as the "generic 64-bit specific x86 processor model":
#
# https://github.com/llvm/llvm-project/blob/e7694f34ab6a12b8bb480cbfcb396d0a64fe965f/llvm/lib/Target/X86/X86.td#L1165-L1187
if target.cpu == 'x86_64':
ldflags.append('-mllvm:-mcpu=x86-64')
# We do not need special flags for arm64. Hooray for fixed-length
# instruction sets.
else:
num_cores = multiprocessing.cpu_count()
cflags.append("-flto")
cflags.append("-flifetime-dse=1")
ldflags.append("-flto=%s" % num_cores)
ldflags.append("-flifetime-dse=1")
return namespace(
enabled=enabled,
cflags=cflags,
ldflags=ldflags,
rust_lto=rust_lto,
)
add_old_configure_assignment('MOZ_LTO', lto.enabled)
set_config('MOZ_LTO', lto.enabled)
set_define('MOZ_LTO', lto.enabled)
set_config('MOZ_LTO_CFLAGS', lto.cflags)
set_config('MOZ_LTO_LDFLAGS', lto.ldflags)
set_config('MOZ_LTO_RUST', lto.rust_lto)
add_old_configure_assignment('MOZ_LTO_CFLAGS', lto.cflags)
add_old_configure_assignment('MOZ_LTO_LDFLAGS', lto.ldflags)