Skip to content

Commit

Permalink
Optimize determine_rpath_dirs
Browse files Browse the repository at this point in the history
When generating targets in ninja backend for my project,
`determine_rpath_dirs` is the second most expensive function
call. Here are some optimizations:

- Use a single regex instead of 3 to find rpath args
- Add cache to get_rpath_dirs_from_link_args
- Remove unneeded isinstance check for get_external_link_args
- early exit when iterating for rpath arguments
- compute system_dirs and external_rpaths once per target instead
  of recomputing them for every link args of the target (!!!)
  • Loading branch information
bruchar1 committed Jan 30, 2025
1 parent c616f1e commit 788a761
Showing 1 changed file with 61 additions and 52 deletions.
113 changes: 61 additions & 52 deletions mesonbuild/backend/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,45 @@ def get_genvslite_backend(genvsname: str, build: T.Optional[build.Build] = None,
return vs2022backend.Vs2022Backend(build, interpreter, gen_lite = True)
return None


# Match rpath formats:
# -Wl,-rpath=
# -Wl,-rpath,
# Match solaris style compat runpath formats:
# -Wl,-R
# -Wl,-R,
# Match symbols formats:
# -Wl,--just-symbols=
# -Wl,--just-symbols,
_rpath_regex = re.compile(r'-Wl,(-rpath[=,]|-R,?|--just-symbols[=,])([^,]+)')

@lru_cache(maxsize=None)
def get_rpath_dirs_from_link_arg(arg: str) -> T.Set[str]:
dirs: T.Set[str] = set()
rpath_match = _rpath_regex.match(arg)
if rpath_match is None:
return dirs

fmt, opts = rpath_match.groups()

if fmt.startswith('-rpath'):
dirs.update(opts.split(':'))

elif fmt.startswith('-R'):
for dir in opts.split(':'):
# The symbols arg is an rpath if the path is a directory
if os.path.isdir(dir):
dirs.add(dir)

else: # fmt.startswith('--just-symbols')
for dir in opts.split(':'):
# Prevent usage of --just-symbols to specify rpath
if os.path.isdir(dir):
raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.')

return dirs


# This class contains the basic functionality that is needed by all backends.
# Feel free to move stuff in and out of it as you see fit.
class Backend:
Expand Down Expand Up @@ -712,89 +751,60 @@ def determine_linker_and_stdlib_args(self, target: build.BuildTarget) -> T.Tuple
l, stdlib_args = target.get_clink_dynamic_linker_and_stdlibs()
return l, stdlib_args

@staticmethod
def _libdir_is_system(libdir: str, compilers: T.Mapping[str, 'Compiler'], env: 'Environment') -> bool:
libdir = os.path.normpath(libdir)
for cc in compilers.values():
if libdir in cc.get_library_dirs(env):
return True
return False

def get_external_rpath_dirs(self, target: build.BuildTarget) -> T.Set[str]:
args: T.List[str] = []
for lang in LANGUAGES_USING_LDFLAGS:
try:
e = self.environment.coredata.get_external_link_args(target.for_machine, lang)
if isinstance(e, str):
args.append(e)
else:
args.extend(e)
except Exception:
args += self.environment.coredata.get_external_link_args(target.for_machine, lang)
except KeyError:
pass
return self.get_rpath_dirs_from_link_args(args)

@staticmethod
def get_rpath_dirs_from_link_args(args: T.List[str]) -> T.Set[str]:
dirs: T.Set[str] = set()
# Match rpath formats:
# -Wl,-rpath=
# -Wl,-rpath,
rpath_regex = re.compile(r'-Wl,-rpath[=,]([^,]+)')
# Match solaris style compat runpath formats:
# -Wl,-R
# -Wl,-R,
runpath_regex = re.compile(r'-Wl,-R[,]?([^,]+)')
# Match symbols formats:
# -Wl,--just-symbols=
# -Wl,--just-symbols,
symbols_regex = re.compile(r'-Wl,--just-symbols[=,]([^,]+)')
for arg in args:
rpath_match = rpath_regex.match(arg)
if rpath_match:
for dir in rpath_match.group(1).split(':'):
dirs.add(dir)
runpath_match = runpath_regex.match(arg)
if runpath_match:
for dir in runpath_match.group(1).split(':'):
# The symbols arg is an rpath if the path is a directory
if Path(dir).is_dir():
dirs.add(dir)
symbols_match = symbols_regex.match(arg)
if symbols_match:
for dir in symbols_match.group(1).split(':'):
# Prevent usage of --just-symbols to specify rpath
if Path(dir).is_dir():
raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.')
if arg.startswith('-Wl,'):
dirs.update(get_rpath_dirs_from_link_arg(arg))
return dirs

@lru_cache(maxsize=None)
def rpaths_for_non_system_absolute_shared_libraries(self, target: build.BuildTarget, exclude_system: bool = True) -> 'ImmutableListProtocol[str]':
paths: OrderedSet[str] = OrderedSet()
srcdir = self.environment.get_source_dir()

system_dirs = set()
if exclude_system:
for cc in target.compilers.values():
system_dirs.update(cc.get_library_dirs(self.environment))

external_rpaths = self.get_external_rpath_dirs(target)

for dep in target.external_deps:
if dep.type_name not in {'library', 'pkgconfig', 'cmake'}:
continue
for libpath in dep.link_args:
if libpath.startswith('-'):
continue
# For all link args that are absolute paths to a library file, add RPATH args
if not os.path.isabs(libpath):
continue
libdir = os.path.dirname(libpath)
if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment):
# No point in adding system paths.
continue
# Don't remove rpaths specified in LDFLAGS.
if libdir in self.get_external_rpath_dirs(target):
continue
libdir, libname = os.path.split(libpath)
# Windows doesn't support rpaths, but we use this function to
# emulate rpaths by setting PATH
# .dll is there for mingw gcc
# .so's may be extended with version information, e.g. libxyz.so.1.2.3
if not (
os.path.splitext(libpath)[1] in {'.dll', '.lib', '.so', '.dylib'}
or re.match(r'.+\.so(\.|$)', os.path.basename(libpath))
libname.endswith(('.dll', '.lib', '.so', '.dylib'))
or '.so.' in libname
):
continue
# Don't remove rpaths specified in LDFLAGS.
if libdir in external_rpaths:
continue
if system_dirs and os.path.normpath(libdir) in system_dirs:
# No point in adding system paths.
continue

try:
commonpath = os.path.commonpath((libdir, srcdir))
Expand All @@ -803,7 +813,6 @@ def rpaths_for_non_system_absolute_shared_libraries(self, target: build.BuildTar

if commonpath == srcdir:
rel_to_src = libdir[len(srcdir) + 1:]
assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute'
paths.add(os.path.join(self.build_to_src, rel_to_src))
else:
paths.add(libdir)
Expand Down

0 comments on commit 788a761

Please sign in to comment.