Skip to content

Commit bde6f2e

Browse files
chbaker0Aravind Vasudevan
authored andcommitted
Build stage0 with bootstrap LLVM and stage1+ with final LLVM
Bug: 1412187 Change-Id: I92465a8cc9bc1df86c5a606c3f8aa3724b7c9b90 Cq-Include-Trybots: luci.chromium.try:linux-rust-x64-dbg,android-rust-arm-rel,android-rust-arm-dbg Change-Id: I92465a8cc9bc1df86c5a606c3f8aa3724b7c9b90 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4219564 Reviewed-by: danakj <[email protected]> Commit-Queue: Collin Baker <[email protected]> Cr-Commit-Position: refs/heads/main@{#1109264} NOKEYCHECK=True GitOrigin-RevId: 612e92866e56831593e0552db497f4b663c6cf99
1 parent cc64955 commit bde6f2e

File tree

3 files changed

+187
-122
lines changed

3 files changed

+187
-122
lines changed

build_rust.py

Lines changed: 182 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def VerifyStage0JsonHash():
180180
sys.exit(1)
181181

182182

183-
def Configure(llvm_bins_path, llvm_libs_root):
183+
def Configure(llvm_bins_path, llvm_libs_root, link_tensorflow):
184184
# Read the config.toml template file...
185185
with open(RUST_CONFIG_TEMPLATE_PATH, 'r') as input:
186186
template = string.Template(input.read())
@@ -194,6 +194,11 @@ def quote_string(s: str):
194194
subs['LLVM_BIN'] = quote_string(str(llvm_bins_path))
195195
subs['PACKAGE_VERSION'] = GetPackageVersionForBuild()
196196

197+
if link_tensorflow:
198+
subs['RUSTC_LLVM_LDFLAGS'] = '-ltf_xla_runtime'
199+
else:
200+
subs['RUSTC_LLVM_LDFLAGS'] = ''
201+
197202
# ...and apply substitutions, writing to config.toml in Rust tree.
198203
with open(os.path.join(RUST_SRC_DIR, 'config.toml'), 'w') as output:
199204
output.write(template.substitute(subs))
@@ -275,104 +280,125 @@ def CargoVendor(cargo_bin):
275280
os.path.join(RUST_SRC_DIR, '.cargo', 'config.toml'))
276281

277282

278-
def RunXPy(sub, args, llvm_bins_path, zlib_path, libxml2_dirs, build_mac_arm,
279-
gcc_toolchain_path, verbose):
280-
''' Run x.py, Rust's build script'''
281-
# We append to these flags, make sure they exist.
282-
ENV_FLAGS = [
283-
'CFLAGS',
284-
'CXXFLAGS',
285-
'LDFLAGS',
286-
'RUSTFLAGS_BOOTSTRAP',
287-
'RUSTFLAGS_NOT_BOOTSTRAP',
288-
'RUSTDOCFLAGS',
289-
]
290-
291-
RUSTENV = collections.defaultdict(str, os.environ)
292-
for f in ENV_FLAGS:
293-
RUSTENV.setdefault(f, '')
294-
295-
# The AR, CC, CXX flags control the C/C++ compiler used through the `cc`
296-
# crate. There are also C/C++ targets that are part of the Rust toolchain
297-
# build for which the tool is controlled from `config.toml`, so these must
298-
# be duplicated there.
299-
300-
if sys.platform == 'win32':
301-
RUSTENV['AR'] = os.path.join(llvm_bins_path, 'llvm-lib.exe')
302-
RUSTENV['CC'] = os.path.join(llvm_bins_path, 'clang-cl.exe')
303-
RUSTENV['CXX'] = os.path.join(llvm_bins_path, 'clang-cl.exe')
304-
else:
305-
RUSTENV['AR'] = os.path.join(llvm_bins_path, 'llvm-ar')
306-
RUSTENV['CC'] = os.path.join(llvm_bins_path, 'clang')
307-
RUSTENV['CXX'] = os.path.join(llvm_bins_path, 'clang++')
308-
309-
if sys.platform == 'darwin':
310-
# The system/xcode compiler would find system SDK correctly, but
311-
# the Clang we've built does not. See
312-
# https://github.com/llvm/llvm-project/issues/45225
313-
sdk_path = subprocess.check_output(['xcrun', '--show-sdk-path'],
314-
text=True).rstrip()
315-
RUSTENV['CFLAGS'] += f' -isysroot {sdk_path}'
316-
RUSTENV['CXXFLAGS'] += f' -isysroot {sdk_path}'
317-
RUSTENV['LDFLAGS'] += f' -isysroot {sdk_path}'
318-
RUSTENV['RUSTFLAGS_BOOTSTRAP'] += (
319-
f' -Clink-arg=-isysroot -Clink-arg={sdk_path}')
320-
RUSTENV['RUSTFLAGS_NOT_BOOTSTRAP'] += (
321-
f' -Clink-arg=-isysroot -Clink-arg={sdk_path}')
322-
# Rust compiletests don't get any of the RUSTFLAGS that we set here and
323-
# then the clang linker can't find `-lSystem`, unless we set the
324-
# `SDKROOT`.
325-
RUSTENV['SDKROOT'] = sdk_path
326-
327-
if zlib_path:
328-
RUSTENV['CFLAGS'] += f' -I{zlib_path}'
329-
RUSTENV['CXXFLAGS'] += f' -I{zlib_path}'
330-
RUSTENV['LDFLAGS'] += f' {LD_PATH_FLAG}{zlib_path}'
331-
RUSTENV['RUSTFLAGS_BOOTSTRAP'] += (
332-
f' -Clink-arg={LD_PATH_FLAG}{zlib_path}')
333-
RUSTENV['RUSTFLAGS_NOT_BOOTSTRAP'] += (
334-
f' -Clink-arg={LD_PATH_FLAG}{zlib_path}')
335-
336-
if libxml2_dirs:
337-
RUSTENV['CFLAGS'] += f' -I{libxml2_dirs.include_dir}'
338-
RUSTENV['CXXFLAGS'] += f' -I{libxml2_dirs.include_dir}'
339-
RUSTENV['LDFLAGS'] += f' {LD_PATH_FLAG}{libxml2_dirs.lib_dir}'
340-
RUSTENV['RUSTFLAGS_BOOTSTRAP'] += (
341-
f' -Clink-arg={LD_PATH_FLAG}{libxml2_dirs.lib_dir}')
342-
RUSTENV['RUSTFLAGS_NOT_BOOTSTRAP'] += (
343-
f' -Clink-arg={LD_PATH_FLAG}{libxml2_dirs.lib_dir}')
344-
345-
if gcc_toolchain_path:
346-
# We use these flags to avoid linking with the system libstdc++.
347-
gcc_toolchain_flag = f'--gcc-toolchain={gcc_toolchain_path}'
348-
RUSTENV['CFLAGS'] += f' {gcc_toolchain_flag}'
349-
RUSTENV['CXXFLAGS'] += f' {gcc_toolchain_flag}'
350-
RUSTENV['LDFLAGS'] += f' {gcc_toolchain_flag}'
351-
# A `-Clink-arg=<foo>` arg passes `foo`` to the linker invovation.
352-
RUSTENV['RUSTFLAGS_BOOTSTRAP'] += f' -Clink-arg={gcc_toolchain_flag}'
353-
RUSTENV[
354-
'RUSTFLAGS_NOT_BOOTSTRAP'] += f' -Clink-arg={gcc_toolchain_flag}'
355-
RUSTENV['RUSTFLAGS_BOOTSTRAP'] += (
356-
f' -L native={gcc_toolchain_path}/lib64')
357-
RUSTENV['RUSTFLAGS_NOT_BOOTSTRAP'] += (
358-
f' -L native={gcc_toolchain_path}/lib64')
359-
360-
# Rustdoc should use our clang linker as well, as we pass flags that
361-
# the system linker may not understand.
362-
RUSTENV['RUSTDOCFLAGS'] += f' -Clinker={RUSTENV["CC"]}'
363-
364-
# Cargo normally stores files in $HOME. Override this.
365-
RUSTENV['CARGO_HOME'] = CARGO_HOME_DIR
366-
367-
# This enables the compiler to produce Mac ARM binaries.
368-
if build_mac_arm:
369-
args.extend(['--target', 'aarch64-apple-darwin'])
370-
371-
os.chdir(RUST_SRC_DIR)
372-
cmd = [sys.executable, 'x.py', sub]
373-
if verbose and verbose > 0:
374-
cmd.append('-' + verbose * 'v')
375-
RunCommand(cmd + args, msvc_arch='x64', env=RUSTENV)
283+
class XPy:
284+
''' Runner for x.py, Rust's build script. Holds shared state between x.py
285+
runs. '''
286+
287+
def __init__(self, llvm_bins_path, zlib_path, libxml2_dirs, build_mac_arm,
288+
gcc_toolchain_path, verbose):
289+
self._env = collections.defaultdict(str, os.environ)
290+
self._build_mac_arm = build_mac_arm
291+
self._verbose = verbose
292+
293+
# We append to these flags, make sure they exist.
294+
ENV_FLAGS = [
295+
'CFLAGS',
296+
'CXXFLAGS',
297+
'LDFLAGS',
298+
'RUSTFLAGS_BOOTSTRAP',
299+
'RUSTFLAGS_NOT_BOOTSTRAP',
300+
'RUSTDOCFLAGS',
301+
]
302+
303+
self._env = collections.defaultdict(str, os.environ)
304+
for f in ENV_FLAGS:
305+
self._env.setdefault(f, '')
306+
307+
# The AR, CC, CXX flags control the C/C++ compiler used through the `cc`
308+
# crate. There are also C/C++ targets that are part of the Rust
309+
# toolchain build for which the tool is controlled from `config.toml`,
310+
# so these must be duplicated there.
311+
312+
if sys.platform == 'win32':
313+
self._env['AR'] = os.path.join(llvm_bins_path, 'llvm-lib.exe')
314+
self._env['CC'] = os.path.join(llvm_bins_path, 'clang-cl.exe')
315+
self._env['CXX'] = os.path.join(llvm_bins_path, 'clang-cl.exe')
316+
else:
317+
self._env['AR'] = os.path.join(llvm_bins_path, 'llvm-ar')
318+
self._env['CC'] = os.path.join(llvm_bins_path, 'clang')
319+
self._env['CXX'] = os.path.join(llvm_bins_path, 'clang++')
320+
321+
if sys.platform == 'darwin':
322+
# The system/xcode compiler would find system SDK correctly, but
323+
# the Clang we've built does not. See
324+
# https://github.com/llvm/llvm-project/issues/45225
325+
sdk_path = subprocess.check_output(['xcrun', '--show-sdk-path'],
326+
text=True).rstrip()
327+
RUSTENV['CFLAGS'] += f' -isysroot {sdk_path}'
328+
RUSTENV['CXXFLAGS'] += f' -isysroot {sdk_path}'
329+
RUSTENV['LDFLAGS'] += f' -isysroot {sdk_path}'
330+
RUSTENV['RUSTFLAGS_BOOTSTRAP'] += (
331+
f' -Clink-arg=-isysroot -Clink-arg={sdk_path}')
332+
RUSTENV['RUSTFLAGS_NOT_BOOTSTRAP'] += (
333+
f' -Clink-arg=-isysroot -Clink-arg={sdk_path}')
334+
# Rust compiletests don't get any of the RUSTFLAGS that we set here
335+
# and then the clang linker can't find `-lSystem`, unless we set the
336+
# `SDKROOT`.
337+
RUSTENV['SDKROOT'] = sdk_path
338+
339+
if zlib_path:
340+
self._env['CFLAGS'] += f' -I{zlib_path}'
341+
self._env['CXXFLAGS'] += f' -I{zlib_path}'
342+
self._env['LDFLAGS'] += f' {LD_PATH_FLAG}{zlib_path}'
343+
self._env['RUSTFLAGS_BOOTSTRAP'] += (
344+
f' -Clink-arg={LD_PATH_FLAG}{zlib_path}')
345+
self._env['RUSTFLAGS_NOT_BOOTSTRAP'] += (
346+
f' -Clink-arg={LD_PATH_FLAG}{zlib_path}')
347+
348+
if libxml2_dirs:
349+
self._env['CFLAGS'] += f' -I{libxml2_dirs.include_dir}'
350+
self._env['CXXFLAGS'] += f' -I{libxml2_dirs.include_dir}'
351+
self._env['LDFLAGS'] += f' {LD_PATH_FLAG}{libxml2_dirs.lib_dir}'
352+
self._env['RUSTFLAGS_BOOTSTRAP'] += (
353+
f' -Clink-arg={LD_PATH_FLAG}{libxml2_dirs.lib_dir}')
354+
self._env['RUSTFLAGS_NOT_BOOTSTRAP'] += (
355+
f' -Clink-arg={LD_PATH_FLAG}{libxml2_dirs.lib_dir}')
356+
357+
if gcc_toolchain_path:
358+
# We use these flags to avoid linking with the system libstdc++.
359+
gcc_toolchain_flag = f'--gcc-toolchain={gcc_toolchain_path}'
360+
self._env['CFLAGS'] += f' {gcc_toolchain_flag}'
361+
self._env['CXXFLAGS'] += f' {gcc_toolchain_flag}'
362+
self._env['LDFLAGS'] += f' {gcc_toolchain_flag}'
363+
# A `-Clink-arg=<foo>` arg passes `foo`` to the linker invovation.
364+
self._env[
365+
'RUSTFLAGS_BOOTSTRAP'] += f' -Clink-arg={gcc_toolchain_flag}'
366+
self._env[
367+
'RUSTFLAGS_NOT_BOOTSTRAP'] += f' -Clink-arg={gcc_toolchain_flag}'
368+
self._env['RUSTFLAGS_BOOTSTRAP'] += (
369+
f' -L native={gcc_toolchain_path}/lib64')
370+
self._env['RUSTFLAGS_NOT_BOOTSTRAP'] += (
371+
f' -L native={gcc_toolchain_path}/lib64')
372+
373+
# Direct rustc to use Chromium's lld instead of the system linker. This
374+
# is critical for stage 1 onward since we need to link libs with LLVM
375+
# bitcode. It is also good for a hermetic build in general.
376+
#
377+
# The `--undefined-version` flag is needed due to a bug in libtest:
378+
# https://github.com/rust-lang/rust/issues/105967
379+
self._env[
380+
'RUSTFLAGS_BOOTSTRAP'] += ' -Clink-arg=-fuse-ld=lld -Clink-arg=-Wl,--undefined-version'
381+
self._env[
382+
'RUSTFLAGS_NOT_BOOTSTRAP'] += ' -Clink-arg=-fuse-ld=lld -Clink-arg=-Wl,--undefined-version'
383+
384+
# Rustdoc should use our clang linker as well, as we pass flags that
385+
# the system linker may not understand.
386+
self._env['RUSTDOCFLAGS'] += f' -Clinker={self._env["CC"]}'
387+
388+
# Cargo normally stores files in $HOME. Override this.
389+
self._env['CARGO_HOME'] = CARGO_HOME_DIR
390+
391+
def run(self, sub, args):
392+
''' Run x.py subcommand with specified args. '''
393+
# This enables the compiler to produce Mac ARM binaries.
394+
if self._build_mac_arm:
395+
args.extend(['--target', 'aarch64-apple-darwin'])
396+
397+
os.chdir(RUST_SRC_DIR)
398+
cmd = [sys.executable, 'x.py', sub]
399+
if self._verbose and self._verbose > 0:
400+
cmd.append('-' + self._verbose * 'v')
401+
RunCommand(cmd + args, msvc_arch='x64', env=self._env)
376402

377403

378404
# Get arguments to run desired test suites, minus disabled tests.
@@ -451,7 +477,8 @@ def main():
451477
'--use-final-llvm-build-dir',
452478
action='store_true',
453479
help='use libs in LLVM_BUILD_DIR instead of LLVM_BOOTSTRAP_DIR. Useful '
454-
'with --fetch-llvm-libs for local builds.')
480+
'with --fetch-llvm-libs for local builds. When enabled, these '
481+
'libraries must not use LTO.')
455482
parser.add_argument(
456483
'--run-xpy',
457484
action='store_true',
@@ -468,14 +495,31 @@ def main():
468495
print('--build-mac-arm only valid on intel to cross-build arm')
469496
return 1
470497

471-
# Get the LLVM root for libs. We use LLVM_BUILD_DIR tools either way.
498+
# A production Rust toolchain will use our final-stage LLVM build. These
499+
# LLVM libs are built with LTO enabled, using the same LLVM revision from an
500+
# earlier build stage as backend. The Rust toolchain we start with (the
501+
# upstream beta release) however cannot process these since it uses an
502+
# earlier LLVM build as its backend.
503+
#
504+
# The Rust toolchain can be bootstrapped using earlier native-code LLVM
505+
# artifacts for its first stage, then using the final LLVM libs for further
506+
# stages. We do this because the bootstrap libs don't support targeting all
507+
# platforms, while the final LLVM libs do.
472508
#
473-
# TODO(https://crbug.com/1245714): use LTO libs from LLVM_BUILD_DIR for
474-
# stage 2+.
509+
# Enable conditionally based on arguments and build host.
510+
#
511+
# TODO(https://crbug.com/1412187): hash out implementation issues on
512+
# non-Linux and enable unconditionally (sans script argument).
513+
use_lto_llvm = False
514+
if not args.use_final_llvm_build_dir and sys.platform.startswith('linux'):
515+
use_lto_llvm = True
516+
517+
# Get the LLVM root for the stage0 build. This normally comes from the LLVM
518+
# bootstrap stage, but for local builds we support using LLVM_BUILD_DIR.
475519
if args.use_final_llvm_build_dir:
476-
llvm_libs_root = LLVM_BUILD_DIR
520+
llvm_bootstrap_root = LLVM_BUILD_DIR
477521
else:
478-
llvm_libs_root = build.LLVM_BOOTSTRAP_DIR
522+
llvm_bootstrap_root = build.LLVM_BOOTSTRAP_DIR
479523

480524
# If we're building for Mac ARM on an x86_64 Mac, we can't use the final
481525
# clang binaries as they don't have x86_64 support. Building them with that
@@ -511,8 +555,9 @@ def main():
511555
# `args.gcc_toolchain` if so.
512556
build.MaybeDownloadHostGcc(args)
513557

514-
# Set up config.toml in Rust source tree to configure build.
515-
Configure(llvm_bins_path, llvm_libs_root)
558+
# Set up config.toml in Rust source tree to configure build for stage0.
559+
# Normally, we will reconfigure later to use the production LLVM libs.
560+
Configure(llvm_bins_path, llvm_bootstrap_root, link_tensorflow=False)
516561

517562
cargo_bin = FetchCargo(checkout_revision)
518563
CargoVendor(cargo_bin)
@@ -538,25 +583,46 @@ def main():
538583
# Cargo depends on OpenSSL.
539584
AddOpenSSLToEnv(args.build_mac_arm)
540585

586+
xpy = XPy(llvm_bins_path, zlib_path, libxml2_dirs, args.build_mac_arm,
587+
args.gcc_toolchain, args.verbose)
588+
541589
if args.run_xpy:
542590
if rest[0] == '--':
543591
rest = rest[1:]
544-
RunXPy(rest[0], rest[1:], llvm_bins_path, zlib_path, libxml2_dirs,
545-
args.build_mac_arm, args.gcc_toolchain, args.verbose)
592+
xpy.run(rest[0], rest[1:])
546593
return 0
547594
else:
548595
assert not rest
549596

550597
if not args.skip_clean:
551598
print('Cleaning build artifacts...')
552-
RunXPy('clean', [], llvm_bins_path, zlib_path, libxml2_dirs,
553-
args.build_mac_arm, args.gcc_toolchain, args.verbose)
599+
xpy.run('clean', [])
600+
601+
# Run the stage0 build separately using the bootstrap LLVM libs. This will
602+
# allow further stages to process the LTO-enabled LLVM libs.
603+
#
604+
# On the other hand, when `use_lto_llvm` is disabled, we always use the
605+
# native-code bootstrap libs.
606+
#
607+
# The build has already been configured above for the bootstrap stage.
608+
xpy.run('build', ['--stage', '0', 'compiler/rustc'])
609+
610+
xpy_args = []
611+
# Reconfigure to use production LLVM libs. After this point we must tell
612+
# x.py to keep the stage0 artifacts, even though config.toml changed.
613+
#
614+
# Note we must link tensorflow into rustc_llvm on Linux, since our LLVM
615+
# build includes it for MLGO. Unfortunately llvm-config does not report this
616+
# dependency. See https://github.com/llvm/llvm-project/issues/60751.
617+
if use_lto_llvm:
618+
Configure(llvm_bins_path,
619+
LLVM_BUILD_DIR,
620+
link_tensorflow=sys.platform.startswith('linux'))
621+
xpy_args = ['--keep-stage', '0']
554622

555623
if not args.skip_test:
556624
print('Running stage 2 tests...')
557-
# Run a subset of tests. Tell x.py to keep the rustc we already built.
558-
RunXPy('test', GetTestArgs(), llvm_bins_path, zlib_path, libxml2_dirs,
559-
args.build_mac_arm, args.gcc_toolchain, args.verbose)
625+
xpy.run('test', ['--stage', '2'] + xpy_args + GetTestArgs())
560626

561627
targets = [
562628
'library/proc_macro', 'library/std', 'src/tools/cargo',
@@ -566,8 +632,7 @@ def main():
566632
# Build stage 2 compiler, tools, and libraries. This should reuse earlier
567633
# stages from the test command (if run).
568634
print('Building stage 2 artifacts...')
569-
RunXPy('build', ['--stage', '2'] + targets, llvm_bins_path, zlib_path,
570-
libxml2_dirs, args.build_mac_arm, args.gcc_toolchain, args.verbose)
635+
xpy.run('build', xpy_args + ['--stage', '2'] + targets)
571636

572637
if args.skip_install:
573638
# Rust is fully built. We can quit.
@@ -578,8 +643,7 @@ def main():
578643
if os.path.exists(RUST_TOOLCHAIN_OUT_DIR):
579644
shutil.rmtree(RUST_TOOLCHAIN_OUT_DIR)
580645

581-
RunXPy('install', DISTRIBUTION_ARTIFACTS, llvm_bins_path, zlib_path,
582-
libxml2_dirs, args.build_mac_arm, args.gcc_toolchain, args.verbose)
646+
xpy.run('install', xpy_args + DISTRIBUTION_ARTIFACTS)
583647

584648
# Copy additional sources required for building stdlib out of
585649
# RUST_TOOLCHAIN_SRC_DIST_DIR.

config.toml.template

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ changelog-seen = 2
88
[llvm]
99
download-ci-llvm = false
1010

11-
# Doesn't affect LLVM build (which we don't do) but rather how rustc_llvm crate
12-
# is linked.
11+
# These don't affect the LLVM build (which we don't do) but rather how the
12+
# rustc_llvm crate is linked.
1313
static-libstdcpp = true
14+
ldflags = "$RUSTC_LLVM_LDFLAGS"
1415

1516
[rust]
1617
download-rustc = false

0 commit comments

Comments
 (0)