linkers: Add support for mold linker

[why]
Support for the relatively new mold linker is missing. If someone wants
to use mold as linker `LDFLAGS="-B/path/to/mold"` has to be added instead
of the usual `CC_LD=mold meson ...` or `CXX_LD=mold meson ...`.

[how]
Allow `mold' as linker for clang and newer GCC versions (that versions
that have support).

The error message can be a bit off, because it is generic for all GNU
like compilers, but I guess that is ok. (i.e. 'mold' is not listed as
possible linker, even if it would be possible for the given compiler.)

[note]
GCC Version 12.0.1 is not sufficient to say `mold` is supported. The
expected release with support will be 12.1.0.
On the other hand people that use the un-released 12.0.1 will probably
have built it from trunk. Allowing 12.0.1 is helping bleeding edge
developers to use mold in Meson already now.

Fixes: #9072

Signed-off-by: Fini Jastrow <ulf.fini.jastrow@desy.de>
pull/10331/head
Fini Jastrow 3 years ago committed by Xavier Claessens
parent 93ed7531c4
commit c16fdaeeca
  1. 1
      docs/markdown/Reference-tables.md
  2. 4
      docs/markdown/snippets/mold-support.md
  3. 4
      mesonbuild/compilers/compilers.py
  4. 4
      mesonbuild/compilers/d.py
  5. 60
      mesonbuild/compilers/detect.py
  6. 8
      mesonbuild/compilers/mixins/clang.py
  7. 4
      mesonbuild/compilers/mixins/clike.py
  8. 10
      mesonbuild/compilers/mixins/gnu.py
  9. 4
      mesonbuild/compilers/rust.py
  10. 14
      mesonbuild/linkers/detect.py
  11. 7
      mesonbuild/linkers/linkers.py
  12. 4
      unittests/linuxliketests.py

@ -46,6 +46,7 @@ These are return values of the `get_linker_id` method in a compiler object.
| ld.bfd | The GNU linker |
| ld.gold | The GNU gold linker |
| ld.lld | The LLVM linker, with the GNU interface |
| ld.mold | The fast MOLD linker |
| ld.solaris | Solaris and illumos |
| ld.wasm | emscripten's wasm-ld linker |
| ld64 | Apple ld64 |

@ -0,0 +1,4 @@
## Support for mold linker added
The high performance linker mold can be selected via `CC_LD` or `CXX_LD` for
Clang and GCC >= 12.0.1.

@ -1,4 +1,4 @@
# Copyright 2012-2019 The Meson development team
# Copyright 2012-2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -1007,7 +1007,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta):
return dep.get_link_args()
@classmethod
def use_linker_args(cls, linker: str) -> T.List[str]:
def use_linker_args(cls, linker: str, version: str) -> T.List[str]:
"""Get a list of arguments to pass to the compiler to set the linker.
"""
return []

@ -1,4 +1,4 @@
# Copyright 2012-2017 The Meson development team
# Copyright 2012-2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -806,7 +806,7 @@ class LLVMDCompiler(DmdLikeCompilerMixin, DCompiler):
return ldc_optimization_args[optimization_level]
@classmethod
def use_linker_args(cls, linker: str) -> T.List[str]:
def use_linker_args(cls, linker: str, version: str) -> T.List[str]:
return [f'-linker={linker}']
def get_linker_always_args(self) -> T.List[str]:

@ -1,4 +1,4 @@
# Copyright 2012-2021 The Meson development team
# Copyright 2012-2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -448,7 +448,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin
version = _get_gnu_version_from_defines(defines)
cls = GnuCCompiler if lang == 'c' else GnuCPPCompiler
linker = guess_nix_linker(env, compiler, cls, for_machine)
linker = guess_nix_linker(env, compiler, cls, version, for_machine)
return cls(
ccache + compiler, version, for_machine, is_cross,
@ -484,7 +484,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin
cls = ArmLtdClangCCompiler
elif lang == 'cpp':
cls = ArmLtdClangCPPCompiler
linker = guess_nix_linker(env, compiler, cls, for_machine)
linker = guess_nix_linker(env, compiler, cls, version, for_machine)
return cls(
ccache + compiler, version, for_machine, is_cross, info,
exe_wrap, linker=linker)
@ -523,7 +523,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin
else:
target = 'unknown target'
cls = ClangClCCompiler if lang == 'c' else ClangClCPPCompiler
linker = guess_win_linker(env, ['lld-link'], cls, for_machine)
linker = guess_win_linker(env, ['lld-link'], cls, version, for_machine)
return cls(
compiler, version, for_machine, is_cross, info, target,
exe_wrap, linker=linker)
@ -544,11 +544,11 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin
# style ld, but for clang on "real" windows we'll use
# either link.exe or lld-link.exe
try:
linker = guess_win_linker(env, compiler, cls, for_machine, invoked_directly=False)
linker = guess_win_linker(env, compiler, cls, version, for_machine, invoked_directly=False)
except MesonException:
pass
if linker is None:
linker = guess_nix_linker(env, compiler, cls, for_machine)
linker = guess_nix_linker(env, compiler, cls, version, for_machine)
return cls(
ccache + compiler, version, for_machine, is_cross, info,
@ -582,7 +582,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin
m = f'Failed to detect MSVC compiler target architecture: \'cl /?\' output is\n{cl_signature}'
raise EnvironmentException(m)
cls = VisualStudioCCompiler if lang == 'c' else VisualStudioCPPCompiler
linker = guess_win_linker(env, ['link'], cls, for_machine)
linker = guess_win_linker(env, ['link'], cls, version, for_machine)
# As of this writing, CCache does not support MSVC but sccache does.
if 'sccache' in ccache:
final_compiler = ccache + compiler
@ -607,7 +607,7 @@ def _detect_c_or_cpp_compiler(env: 'Environment', lang: str, for_machine: Machin
info, exe_wrap, linker=linker)
if '(ICC)' in out:
cls = IntelCCompiler if lang == 'c' else IntelCPPCompiler
l = guess_nix_linker(env, compiler, cls, for_machine)
l = guess_nix_linker(env, compiler, cls, version, for_machine)
return cls(
ccache + compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=l)
@ -731,14 +731,14 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C
if guess_gcc_or_lcc == 'lcc':
version = _get_lcc_version_from_defines(defines)
cls = ElbrusFortranCompiler
linker = guess_nix_linker(env, compiler, cls, for_machine)
linker = guess_nix_linker(env, compiler, cls, version, for_machine)
return cls(
compiler, version, for_machine, is_cross, info,
exe_wrap, defines, full_version=full_version, linker=linker)
else:
version = _get_gnu_version_from_defines(defines)
cls = GnuFortranCompiler
linker = guess_nix_linker(env, compiler, cls, for_machine)
linker = guess_nix_linker(env, compiler, cls, version, for_machine)
return cls(
compiler, version, for_machine, is_cross, info,
exe_wrap, defines, full_version=full_version, linker=linker)
@ -750,13 +750,13 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C
arm_ver_minor = arm_ver_match.group(2)
arm_ver_build = arm_ver_match.group(3)
version = '.'.join([arm_ver_major, arm_ver_minor, arm_ver_build])
linker = guess_nix_linker(env, compiler, cls, for_machine)
linker = guess_nix_linker(env, compiler, cls, version, for_machine)
return cls(
ccache + compiler, version, for_machine, is_cross, info,
exe_wrap, linker=linker)
if 'G95' in out:
cls = G95FortranCompiler
linker = guess_nix_linker(env, compiler, cls, for_machine)
linker = guess_nix_linker(env, compiler, cls, version, for_machine)
return G95FortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -764,7 +764,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C
if 'Sun Fortran' in err:
version = search_version(err)
cls = SunFortranCompiler
linker = guess_nix_linker(env, compiler, cls, for_machine)
linker = guess_nix_linker(env, compiler, cls, version, for_machine)
return SunFortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -780,7 +780,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C
target, exe_wrap, linker=linker)
if 'ifort (IFORT)' in out:
linker = guess_nix_linker(env, compiler, IntelFortranCompiler, for_machine)
linker = guess_nix_linker(env, compiler, IntelFortranCompiler, version, for_machine)
return IntelFortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -810,14 +810,14 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C
if 'flang' in out or 'clang' in out:
linker = guess_nix_linker(env,
compiler, FlangFortranCompiler, for_machine)
compiler, FlangFortranCompiler, version, for_machine)
return FlangFortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
if 'Open64 Compiler Suite' in err:
linker = guess_nix_linker(env,
compiler, Open64FortranCompiler, for_machine)
compiler, Open64FortranCompiler, version, for_machine)
return Open64FortranCompiler(
compiler, version, for_machine, is_cross, info,
exe_wrap, full_version=full_version, linker=linker)
@ -865,7 +865,7 @@ def _detect_objc_or_objcpp_compiler(env: 'Environment', for_machine: MachineChoi
continue
version = _get_gnu_version_from_defines(defines)
comp = GnuObjCCompiler if objc else GnuObjCPPCompiler
linker = guess_nix_linker(env, compiler, comp, for_machine)
linker = guess_nix_linker(env, compiler, comp, version, for_machine)
return comp(
ccache + compiler, version, for_machine, is_cross, info,
exe_wrap, defines, linker=linker)
@ -882,12 +882,12 @@ def _detect_objc_or_objcpp_compiler(env: 'Environment', for_machine: MachineChoi
if 'windows' in out or env.machines[for_machine].is_windows():
# If we're in a MINGW context this actually will use a gnu style ld
try:
linker = guess_win_linker(env, compiler, comp, for_machine)
linker = guess_win_linker(env, compiler, comp, version, for_machine)
except MesonException:
pass
if not linker:
linker = guess_nix_linker(env, compiler, comp, for_machine)
linker = guess_nix_linker(env, compiler, comp, version, for_machine)
return comp(
ccache + compiler, version, for_machine,
is_cross, info, exe_wrap, linker=linker, defines=defines)
@ -1032,7 +1032,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
extra_args: T.Dict[str, T.Union[str, bool]] = {}
always_args: T.List[str] = []
if is_link_exe:
compiler.extend(cls.use_linker_args(cc.linker.exelist[0]))
compiler.extend(cls.use_linker_args(cc.linker.exelist[0], ''))
extra_args['direct'] = True
extra_args['machine'] = cc.linker.machine
else:
@ -1040,7 +1040,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
if 'ccache' in exelist[0]:
del exelist[0]
c = exelist.pop(0)
compiler.extend(cls.use_linker_args(c))
compiler.extend(cls.use_linker_args(c, ''))
# Also ensure that we pass any extra arguments to the linker
for l in exelist:
@ -1059,12 +1059,12 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
**extra_args)
elif 'link' in override[0]:
linker = guess_win_linker(env,
override, cls, for_machine, use_linker_prefix=False)
override, cls, version, for_machine, use_linker_prefix=False)
# rustc takes linker arguments without a prefix, and
# inserts the correct prefix itself.
assert isinstance(linker, VisualStudioLikeLinkerMixin)
linker.direct = True
compiler.extend(cls.use_linker_args(linker.exelist[0]))
compiler.extend(cls.use_linker_args(linker.exelist[0], ''))
else:
# On linux and macos rust will invoke the c compiler for
# linking, on windows it will use lld-link or link.exe.
@ -1076,7 +1076,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
# Of course, we're not going to use any of that, we just
# need it to get the proper arguments to pass to rustc
c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0]
compiler.extend(cls.use_linker_args(c))
compiler.extend(cls.use_linker_args(c, ''))
env.coredata.add_lang_args(cls.language, cls, for_machine, env)
return cls(
@ -1135,7 +1135,7 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile
objfile = os.path.basename(f)[:-1] + 'obj'
linker = guess_win_linker(env,
exelist,
LLVMDCompiler, for_machine,
LLVMDCompiler, full_version, for_machine,
use_linker_prefix=True, invoked_directly=False,
extra_args=[f])
else:
@ -1143,7 +1143,7 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile
# Clean it up.
objfile = os.path.basename(f)[:-1] + 'o'
linker = guess_nix_linker(env,
exelist, LLVMDCompiler, for_machine,
exelist, LLVMDCompiler, full_version, for_machine,
extra_args=[f])
finally:
windows_proof_rm(f)
@ -1153,7 +1153,7 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile
exelist, version, for_machine, info, arch,
full_version=full_version, linker=linker, version_output=out)
elif 'gdc' in out:
linker = guess_nix_linker(env, exelist, GnuDCompiler, for_machine)
linker = guess_nix_linker(env, exelist, GnuDCompiler, version, for_machine)
return GnuDCompiler(
exelist, version, for_machine, info, arch,
exe_wrapper=exe_wrap, is_cross=is_cross,
@ -1173,12 +1173,12 @@ def detect_d_compiler(env: 'Environment', for_machine: MachineChoice) -> Compile
if info.is_windows() or info.is_cygwin():
objfile = os.path.basename(f)[:-1] + 'obj'
linker = guess_win_linker(env,
exelist, DmdDCompiler, for_machine,
exelist, DmdDCompiler, full_version, for_machine,
invoked_directly=False, extra_args=[f, arch_arg])
else:
objfile = os.path.basename(f)[:-1] + 'o'
linker = guess_nix_linker(env,
exelist, DmdDCompiler, for_machine,
exelist, DmdDCompiler, full_version, for_machine,
extra_args=[f, arch_arg])
finally:
windows_proof_rm(f)
@ -1209,7 +1209,7 @@ def detect_swift_compiler(env: 'Environment', for_machine: MachineChoice) -> Com
# As for 5.0.1 swiftc *requires* a file to check the linker:
with tempfile.NamedTemporaryFile(suffix='.swift') as f:
linker = guess_nix_linker(env,
exelist, SwiftCompiler, for_machine,
exelist, SwiftCompiler, version, for_machine,
extra_args=[f.name])
return SwiftCompiler(
exelist, version, for_machine, is_cross, info, linker=linker)

@ -1,4 +1,4 @@
# Copyright 2019 The meson development team
# Copyright 2019-2022 The meson development team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -118,7 +118,7 @@ class ClangCompiler(GnuLikeCompiler):
return []
@classmethod
def use_linker_args(cls, linker: str) -> T.List[str]:
def use_linker_args(cls, linker: str, version: str) -> T.List[str]:
# Clang additionally can use a linker specified as a path, which GCC
# (and other gcc-like compilers) cannot. This is because clang (being
# llvm based) is retargetable, while GCC is not.
@ -127,13 +127,15 @@ class ClangCompiler(GnuLikeCompiler):
# qcld: Qualcomm Snapdragon linker, based on LLVM
if linker == 'qcld':
return ['-fuse-ld=qcld']
if linker == 'mold':
return ['-fuse-ld=mold']
if shutil.which(linker):
if not shutil.which(linker):
raise mesonlib.MesonException(
f'Cannot find linker {linker}.')
return [f'-fuse-ld={linker}']
return super().use_linker_args(linker)
return super().use_linker_args(linker, version)
def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]:
# Clang only warns about unknown or ignored attributes, so force an

@ -1,4 +1,4 @@
# Copyright 2012-2017 The Meson development team
# Copyright 2012-2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -410,7 +410,7 @@ class CLikeCompiler(Compiler):
if mode is CompileCheckMode.LINK:
ld_value = env.lookup_binary_entry(self.for_machine, self.language + '_ld')
if ld_value is not None:
largs += self.use_linker_args(ld_value[0])
largs += self.use_linker_args(ld_value[0], self.version)
# Add LDFLAGS from the env
sys_ld_args = env.coredata.get_external_link_args(self.for_machine, self.language)

@ -1,4 +1,4 @@
# Copyright 2019 The meson development team
# Copyright 2019-2022 The meson development team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -306,7 +306,7 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta):
return ['-I' + path]
@classmethod
def use_linker_args(cls, linker: str) -> T.List[str]:
def use_linker_args(cls, linker: str, version: str) -> T.List[str]:
if linker not in {'gold', 'bfd', 'lld'}:
raise mesonlib.MesonException(
f'Unsupported linker, only bfd, gold, and lld are supported, not {linker}.')
@ -389,3 +389,9 @@ class GnuCompiler(GnuLikeCompiler):
elif threads > 0:
return [f'-flto={threads}']
return super().get_lto_compile_args(threads=threads)
@classmethod
def use_linker_args(cls, linker: str, version: str) -> T.List[str]:
if linker == 'mold' and mesonlib.version_compare(version, '>=12.0.1'):
return ['-fuse-ld=mold']
return super().use_linker_args(linker, version)

@ -1,4 +1,4 @@
# Copyright 2012-2017 The Meson development team
# Copyright 2012-2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -136,7 +136,7 @@ class RustCompiler(Compiler):
return ['-o', outputname]
@classmethod
def use_linker_args(cls, linker: str) -> T.List[str]:
def use_linker_args(cls, linker: str, version: str) -> T.List[str]:
return ['-C', f'linker={linker}']
# Rust does not have a use_linker_args because it dispatches to a gcc-like

@ -1,4 +1,4 @@
# Copyright 2012-2021 The Meson development team
# Copyright 2012-2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ from .linkers import (
AppleDynamicLinker,
GnuGoldDynamicLinker,
GnuBFDDynamicLinker,
MoldDynamicLinker,
LLVMDynamicLinker,
QualcommLLVMDynamicLinker,
MSVCDynamicLinker,
@ -56,7 +57,7 @@ def __failed_to_detect_linker(compiler: T.List[str], args: T.List[str], stdout:
def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Type['Compiler'],
for_machine: MachineChoice, *,
comp_version: str, for_machine: MachineChoice, *,
use_linker_prefix: bool = True, invoked_directly: bool = True,
extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker':
env.coredata.add_lang_args(comp_class.language, comp_class, for_machine, env)
@ -74,7 +75,7 @@ def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty
override = [] # type: T.List[str]
value = env.lookup_binary_entry(for_machine, comp_class.language + '_ld')
if value is not None:
override = comp_class.use_linker_args(value[0])
override = comp_class.use_linker_args(value[0], comp_version)
check_args += override
if extra_args is not None:
@ -126,12 +127,13 @@ def guess_win_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty
__failed_to_detect_linker(compiler, check_args, o, e)
def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Type['Compiler'],
for_machine: MachineChoice, *,
comp_version:str, for_machine: MachineChoice, *,
extra_args: T.Optional[T.List[str]] = None) -> 'DynamicLinker':
"""Helper for guessing what linker to use on Unix-Like OSes.
:compiler: Invocation to use to get linker
:comp_class: The Compiler Type (uninstantiated)
:comp_version: The compiler version string
:for_machine: which machine this linker targets
:extra_args: Any additional arguments required (such as a source file)
"""
@ -147,7 +149,7 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty
override = [] # type: T.List[str]
value = env.lookup_binary_entry(for_machine, comp_class.language + '_ld')
if value is not None:
override = comp_class.use_linker_args(value[0])
override = comp_class.use_linker_args(value[0], comp_version)
check_args += override
_, o, e = Popen_safe(compiler + check_args)
@ -194,6 +196,8 @@ def guess_nix_linker(env: 'Environment', compiler: T.List[str], comp_class: T.Ty
cls: T.Type[GnuDynamicLinker]
if 'gold' in o or 'gold' in e:
cls = GnuGoldDynamicLinker
elif 'mold' in o or 'mold' in e:
cls = MoldDynamicLinker
else:
cls = GnuBFDDynamicLinker
linker = cls(compiler, for_machine, comp_class.LINKER_PREFIX, override, version=v)

@ -1,4 +1,4 @@
# Copyright 2012-2017 The Meson development team
# Copyright 2012-2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -806,6 +806,11 @@ class GnuBFDDynamicLinker(GnuDynamicLinker):
id = 'ld.bfd'
class MoldDynamicLinker(GnuDynamicLinker):
id = 'ld.mold'
class LLVMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker):
"""Representation of LLVM's ld.lld linker.

@ -1,4 +1,4 @@
# Copyright 2016-2021 The Meson development team
# Copyright 2016-2022 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -1597,7 +1597,7 @@ class LinuxlikeTests(BasePlatformTests):
if isinstance(comp, (AppleClangCCompiler, AppleClangCPPCompiler,
AppleClangObjCCompiler, AppleClangObjCPPCompiler)):
raise SkipTest('AppleClang is currently only supported with ld64')
if lang != 'rust' and comp.use_linker_args('bfd') == []:
if lang != 'rust' and comp.use_linker_args('bfd', '') == []:
raise SkipTest(
f'Compiler {comp.id} does not support using alternative linkers')
self.assertEqual(comp.linker.id, expected)

Loading…
Cancel
Save