Skip to content

Commit 5521f94

Browse files
authored
Merge pull request ClangBuiltLinux#317 from nathanchance/multicall
tc-build: Introduce '--multicall'
2 parents aaec068 + 84b82a6 commit 5521f94

File tree

2 files changed

+86
-18
lines changed

2 files changed

+86
-18
lines changed

build-llvm.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,14 @@
236236
'''),
237237
type=str,
238238
choices=['thin', 'full'])
239+
parser.add_argument('-m',
240+
'--multicall',
241+
help=textwrap.dedent('''\
242+
Build LLVM as a multicall binary via the LLVM_TOOL_LLVM_DRIVER_BUILD CMake option.
243+
This results in a much smaller installation on disk.
244+
245+
'''),
246+
action='store_true')
239247
parser.add_argument('-n',
240248
'--no-update',
241249
help=textwrap.dedent('''\
@@ -536,6 +544,8 @@
536544
if args.vendor_string:
537545
common_cmake_defines['CLANG_VENDOR'] = args.vendor_string
538546
common_cmake_defines['LLD_VENDOR'] = args.vendor_string
547+
if args.multicall:
548+
common_cmake_defines['LLVM_TOOL_LLVM_DRIVER_BUILD'] = 'ON'
539549
if args.defines:
540550
defines = dict(define.split('=', 1) for define in args.defines)
541551
common_cmake_defines.update(defines)

tc_build/llvm.py

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,27 @@ def bolt_clang(self):
7474

7575
tc_build.utils.print_header(f"Performing BOLT with {mode}")
7676

77-
# clang-#: original binary
78-
# clang.bolt: BOLT optimized binary
77+
# real_binary: clang-# or llvm (if multicall is enabled)
78+
# bolt_binary: clang.bolt or llvm.bolt
7979
# .bolt will become original binary after optimization
80-
clang = Path(self.folders.build, 'bin/clang').resolve()
81-
clang_bolt = clang.with_name('clang.bolt')
80+
real_binary = Path(self.folders.build, 'bin/clang').resolve()
81+
binary_prefix = 'llvm' if self.multicall_is_enabled() else 'clang'
82+
bolt_binary = real_binary.with_name(f"{binary_prefix}.bolt")
8283

83-
bolt_profile = Path(self.folders.build, 'clang.fdata')
84+
bolt_profile = Path(self.folders.build, f"{binary_prefix}.fdata")
8485

8586
if mode == 'instrumentation':
86-
# clang.inst: instrumented binary, will be removed after generating profiles
87-
clang_inst = clang.with_name('clang.inst')
87+
# clang.inst / llvm.inst: instrumented binary, will be removed after generating profiles
88+
inst_binary = real_binary.with_name(f"{binary_prefix}.inst")
8889

8990
clang_inst_cmd = [
9091
self.tools.llvm_bolt,
9192
'--instrument',
9293
f"--instrumentation-file={bolt_profile}",
9394
'--instrumentation-file-append-pid',
9495
'-o',
95-
clang_inst,
96-
clang,
96+
inst_binary,
97+
real_binary,
9798
]
9899
# When running an instrumented binary on certain platforms (namely
99100
# Apple Silicon), there may be hangs due to instrumentation in
@@ -104,7 +105,18 @@ def bolt_clang(self):
104105
clang_inst_cmd.append('--conservative-instrumentation')
105106
self.run_cmd(clang_inst_cmd)
106107

107-
self.bolt_builder.bolt_instrumentation = True
108+
if binary_prefix == 'llvm':
109+
# The multicall tools are all symlinked to the 'llvm' binary.
110+
# To avoid having to mess with those symlinks, perform a
111+
# shuffle of the real binary with the instrumented binary for
112+
# the build process.
113+
orig_binary = real_binary.with_name('llvm.orig')
114+
real_binary.replace(orig_binary) # mv llvm llvm.orig
115+
inst_binary.replace(real_binary) # mv llvm.inst llvm
116+
else:
117+
# This option changes CC when building Linux, which is only
118+
# needed when just clang is instrumented.
119+
self.bolt_builder.bolt_instrumentation = True
108120

109121
if mode == 'sampling':
110122
self.bolt_builder.bolt_sampling_output = Path(self.folders.build, 'perf.data')
@@ -115,6 +127,11 @@ def bolt_clang(self):
115127
# With instrumentation, we need to combine the profiles we generated,
116128
# as they are separated by PID
117129
if mode == 'instrumentation':
130+
# Undo shuffle from above
131+
if binary_prefix == 'llvm':
132+
real_binary.replace(inst_binary) # mv llvm llvm.inst
133+
orig_binary.replace(real_binary) # mv llvm.orig llvm
134+
118135
fdata_files = bolt_profile.parent.glob(f"{bolt_profile.name}.*.fdata")
119136

120137
# merge-fdata will print one line for each .fdata it merges.
@@ -139,7 +156,7 @@ def bolt_clang(self):
139156
self.bolt_builder.bolt_sampling_output,
140157
'-o',
141158
bolt_profile,
142-
clang,
159+
real_binary,
143160
]
144161
self.run_cmd(perf2bolt_cmd)
145162
self.bolt_builder.bolt_sampling_output.unlink()
@@ -166,18 +183,18 @@ def bolt_clang(self):
166183
'--dyno-stats',
167184
f"--icf={icf_val}",
168185
'-o',
169-
clang_bolt,
186+
bolt_binary,
170187
f"--reorder-blocks={'cache+' if use_cache_plus else 'ext-tsp'}",
171188
f"--reorder-functions={reorder_funcs_val}",
172189
'--split-all-cold',
173190
f"--split-functions{'=3' if use_sf_val else ''}",
174191
'--use-gnu-stack',
175-
clang,
192+
real_binary,
176193
]
177194
self.run_cmd(clang_opt_cmd)
178-
clang_bolt.replace(clang)
195+
bolt_binary.replace(real_binary)
179196
if mode == 'instrumentation':
180-
clang_inst.unlink()
197+
inst_binary.unlink()
181198

182199
def build(self):
183200
if not self.folders.build:
@@ -388,6 +405,26 @@ def host_target(self):
388405
def host_target_is_enabled(self):
389406
return 'all' in self.targets or self.host_target() in self.targets
390407

408+
def llvm_driver_binaries(self, project):
409+
# Find all CMakeLists.txt for LLVM or clang tools that have multicall driver support
410+
cmakelists_txts = [
411+
cmakelists_txt
412+
for path in Path(self.folders.source, project).glob('tools/*/CMakeLists.txt')
413+
if ' GENERATE_DRIVER' in (cmakelists_txt := path.read_text(encoding='utf-8'))
414+
]
415+
skip_tools = (
416+
# llvm-mt depends on libxml2, which we explicitly do not link against
417+
'llvm-mt', )
418+
# Return the values of the add_clang_tool() or add_llvm_tool() CMake macros
419+
return [
420+
tool for cmakelists_txt in cmakelists_txts
421+
if (match := re.search(r"^add_(?:clang|llvm)_tool\((.*)$", cmakelists_txt, flags=re.M))
422+
and (tool := match.groups()[0]) not in skip_tools
423+
]
424+
425+
def multicall_is_enabled(self):
426+
return self.cmake_defines.get('LLVM_TOOL_LLVM_DRIVER_BUILD', 'OFF') == 'ON'
427+
391428
def project_is_enabled(self, project):
392429
return 'all' in self.projects or project in self.projects
393430

@@ -495,10 +532,22 @@ def configure(self):
495532
'llvm-readelf',
496533
'llvm-strip',
497534
]
535+
# If multicall is enabled, we need to add all possible tools to the
536+
# distribution components list to prevent them from being built as
537+
# standalone tools, which may break the build for tools like
538+
# llvm-symbolizer because they need LLVMDebuginfod but it is not
539+
# linked in that configuration. While this does build a little more
540+
# code for the 'distribution' target, it should result in only a
541+
# slight increase in installation size due to being a multicall
542+
# binary.
543+
if self.multicall_is_enabled():
544+
distribution_components += [item for item in self.llvm_driver_binaries('llvm') if item not in distribution_components]
498545
if self.project_is_enabled('bolt'):
499546
distribution_components.append('bolt')
500547
if self.project_is_enabled('clang'):
501548
distribution_components += ['clang', 'clang-resource-headers']
549+
if self.multicall_is_enabled():
550+
distribution_components += [item for item in self.llvm_driver_binaries('clang') if item not in distribution_components]
502551
if self.project_is_enabled('lld'):
503552
distribution_components.append('lld')
504553
if build_compiler_rt:
@@ -573,16 +622,18 @@ def __init__(self):
573622

574623
self.cmake_defines['LLVM_BUILD_INSTRUMENTED'] = 'IR'
575624
self.cmake_defines['LLVM_BUILD_RUNTIME'] = 'OFF'
576-
self.cmake_defines['LLVM_LINK_LLVM_DYLIB'] = 'ON'
577625

578626
def configure(self):
627+
no_multicall = not self.multicall_is_enabled()
579628
# The following defines are needed to avoid thousands of warnings
580629
# along the lines of:
581630
# "Unable to track new values: Running out of static counters."
582-
# They require LLVM_LINK_DYLIB to be enabled, which is done above.
631+
# LLVM_VP_COUNTERS_PER_SITE requires LLVM_LINK_DYLIB, which is only
632+
# done when multicall is not enabled. If multicall is enabled, we need
633+
# to use CMAKE_C{,XX}_FLAGS.
583634
cmake_options = Path(self.folders.source, 'llvm/cmake/modules/HandleLLVMOptions.cmake')
584635
cmake_text = cmake_options.read_text(encoding='utf-8')
585-
if 'LLVM_VP_COUNTERS_PER_SITE' in cmake_text:
636+
if no_multicall and 'LLVM_VP_COUNTERS_PER_SITE' in cmake_text:
586637
self.cmake_defines['LLVM_VP_COUNTERS_PER_SITE'] = '6'
587638
else:
588639
cflags = []
@@ -605,6 +656,13 @@ def configure(self):
605656
self.cmake_defines['CMAKE_C_FLAGS'] = ' '.join(cflags)
606657
self.cmake_defines['CMAKE_CXX_FLAGS'] = ' '.join(cxxflags)
607658

659+
# These are currently incompatible:
660+
# https://github.com/llvm/llvm-project/pull/133596
661+
# But that should not matter much in this case because multicall uses
662+
# much less disk space.
663+
if no_multicall:
664+
self.cmake_defines['LLVM_LINK_LLVM_DYLIB'] = 'ON'
665+
608666
super().configure()
609667

610668
def generate_profdata(self):

0 commit comments

Comments
 (0)