From c21b04ba087189472c60ce6e84987b74e9b7fccd Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Sat, 14 Nov 2020 16:43:00 +0200 Subject: Add prelinking support for static libraries. --- docs/markdown/Reference-manual.md | 4 ++++ docs/markdown/snippets/prelink.md | 6 ++++++ mesonbuild/backend/ninjabackend.py | 21 ++++++++++++++++++++- mesonbuild/build.py | 22 +++++++++++++++++++++- mesonbuild/compilers/compilers.py | 2 ++ mesonbuild/compilers/mixins/gnu.py | 3 +++ run_unittests.py | 26 ++++++++++++++++++++++++++ test cases/unit/87 prelinking/file1.c | 14 ++++++++++++++ test cases/unit/87 prelinking/file2.c | 9 +++++++++ test cases/unit/87 prelinking/file3.c | 9 +++++++++ test cases/unit/87 prelinking/file4.c | 9 +++++++++ test cases/unit/87 prelinking/main.c | 10 ++++++++++ test cases/unit/87 prelinking/meson.build | 8 ++++++++ test cases/unit/87 prelinking/private_header.h | 11 +++++++++++ test cases/unit/87 prelinking/public_header.h | 3 +++ 15 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 docs/markdown/snippets/prelink.md create mode 100644 test cases/unit/87 prelinking/file1.c create mode 100644 test cases/unit/87 prelinking/file2.c create mode 100644 test cases/unit/87 prelinking/file3.c create mode 100644 test cases/unit/87 prelinking/file4.c create mode 100644 test cases/unit/87 prelinking/main.c create mode 100644 test cases/unit/87 prelinking/meson.build create mode 100644 test cases/unit/87 prelinking/private_header.h create mode 100644 test cases/unit/87 prelinking/public_header.h diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index aeaeccb..f490f5f 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1574,6 +1574,10 @@ has one argument the others don't have: option has no effect on Windows and OS X since it doesn't make sense on Windows and PIC cannot be disabled on OS X. +- `prelink` *since0.57.0*: if `true` the object files in the target + will be prelinked, meaning that it will contain only one prelinked + object file rather than the individual object files. + ### subdir() ``` meson diff --git a/docs/markdown/snippets/prelink.md b/docs/markdown/snippets/prelink.md new file mode 100644 index 0000000..26f5bbb --- /dev/null +++ b/docs/markdown/snippets/prelink.md @@ -0,0 +1,6 @@ +## Add support for prelinked static libraries + +The static library gains a new `prelink` keyword argument that can be +used to prelink object files in that target. This is currently only +supported for the GNU toolchain, patches to add it to other compilers +are most welcome. diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index b03954a..0fe8a2f 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -840,7 +840,11 @@ int dummy; for src in self.generate_unity_files(target, unity_src): obj_list.append(self.generate_single_compile(target, src, True, unity_deps + header_deps)) linker, stdlib_args = self.determine_linker_and_stdlib_args(target) - elem = self.generate_link(target, outname, obj_list, linker, pch_objects, stdlib_args=stdlib_args) + if isinstance(target, build.StaticLibrary) and target.prelink: + final_obj_list = self.generate_prelink(target, obj_list) + else: + final_obj_list = obj_list + elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args) self.generate_shlib_aliases(target, self.get_target_dir(target)) self.add_build(elem) @@ -2683,6 +2687,21 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return guessed_dependencies + absolute_libs + def generate_prelink(self, target, obj_list): + assert(isinstance(target, build.StaticLibrary)) + prelink_name = os.path.join(self.get_target_private_dir(target), target.name + '-prelink.o') + elem = NinjaBuildElement(self.all_outputs, [prelink_name], 'CUSTOM_COMMAND', obj_list) + + prelinker = target.get_prelinker() + cmd = prelinker.exelist[:] + cmd += prelinker.get_prelink_args(prelink_name, obj_list) + + cmd = self.replace_paths(target, cmd) + elem.add_item('COMMAND', cmd) + elem.add_item('description', 'Prelinking {}.'.format(prelink_name)) + self.add_build(elem) + return [prelink_name] + def generate_link(self, target, outname, obj_list, linker, extra_args=None, stdlib_args=None): extra_args = extra_args if extra_args is not None else [] stdlib_args = stdlib_args if stdlib_args is not None else [] diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 36d4e19..5b7a679 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -102,7 +102,7 @@ known_build_target_kwargs = ( known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie'} known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'} known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs'} -known_stlib_kwargs = known_build_target_kwargs | {'pic'} +known_stlib_kwargs = known_build_target_kwargs | {'pic', 'prelink'} known_jar_kwargs = known_exe_kwargs | {'main_class'} @lru_cache(maxsize=None) @@ -1280,6 +1280,23 @@ You probably should put it in link_with instead.''') return langs + def get_prelinker(self): + all_compilers = self.environment.coredata.compilers[self.for_machine] + if self.link_language: + comp = all_compilers[self.link_language] + return comp + for l in clink_langs: + if l in self.compilers: + try: + prelinker = all_compilers[l] + except KeyError: + raise MesonException( + 'Could not get a prelinker linker for build target {!r}. ' + 'Requires a compiler for language "{}", but that is not ' + 'a project language.'.format(self.name, l)) + return prelinker + raise MesonException('Could not determine prelinker for {!r}.'.format(self.name)) + def get_clink_dynamic_linker_and_stdlibs(self): ''' We use the order of languages in `clink_langs` to determine which @@ -1674,6 +1691,9 @@ class StaticLibrary(BuildTarget): self.suffix = 'a' self.filename = self.prefix + self.name + '.' + self.suffix self.outputs = [self.filename] + self.prelink = kwargs.get('prelink', False) + if not isinstance(self.prelink, bool): + raise InvalidArguments('Prelink keyword argument must be a boolean.') def get_link_deps_mapping(self, prefix, environment): return {} diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 5c9e1ae..2900e19 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1200,6 +1200,8 @@ class Compiler(metaclass=abc.ABCMeta): # TODO: using a TypeDict here would improve this raise EnvironmentError('{} does not implement get_feature_args'.format(self.id)) + def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.List[str]: + raise EnvironmentException('{} does not know how to do prelinking.'.format(self.id)) def get_args_from_envvars(lang: str, for_machine: MachineChoice, diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py index bb1fc66..4024cbe 100644 --- a/mesonbuild/compilers/mixins/gnu.py +++ b/mesonbuild/compilers/mixins/gnu.py @@ -394,3 +394,6 @@ class GnuCompiler(GnuLikeCompiler): # GCC only warns about unknown or ignored attributes, so force an # error. return ['-Werror=attributes'] + + def get_prelink_args(self, prelink_name: str, obj_list: T.List[str]) -> T.List[str]: + return ['-r', '-o', prelink_name] + obj_list diff --git a/run_unittests.py b/run_unittests.py index 9560319..a461df5 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -7389,6 +7389,32 @@ class LinuxlikeTests(BasePlatformTests): content = f.read() self.assertNotIn('-lfoo', content) + def test_prelinking(self): + # Prelinking currently only works on recently new GNU toolchains. + # Skip everything else. When support for other toolchains is added, + # remove limitations as necessary. + if is_osx(): + raise unittest.SkipTest('Prelinking not supported on Darwin.') + if 'clang' in os.environ.get('CC', 'dummy'): + raise unittest.SkipTest('Prelinking not supported with Clang.') + gccver = subprocess.check_output(['cc', '--version']) + if b'7.5.0' in gccver: + raise unittest.SkipTest('GCC on Bionic is too old to be supported.') + testdir = os.path.join(self.unit_test_dir, '87 prelinking') + self.init(testdir) + self.build() + outlib = os.path.join(self.builddir, 'libprelinked.a') + ar = shutil.which('ar') + self.assertTrue(os.path.exists(outlib)) + self.assertTrue(ar is not None) + p = subprocess.run([ar, 't', outlib], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + universal_newlines=True, timeout=1) + obj_files = p.stdout.strip().split('\n') + self.assertEqual(len(obj_files), 1) + self.assertTrue(obj_files[0].endswith('-prelink.o')) + class BaseLinuxCrossTests(BasePlatformTests): # Don't pass --libdir when cross-compiling. We have tests that # check whether meson auto-detects it correctly. diff --git a/test cases/unit/87 prelinking/file1.c b/test cases/unit/87 prelinking/file1.c new file mode 100644 index 0000000..9f0e265 --- /dev/null +++ b/test cases/unit/87 prelinking/file1.c @@ -0,0 +1,14 @@ +#include +#include + +int public_func() { + return round1_a(); +} + +int round1_a() { + return round1_b(); +} + +int round2_a() { + return round2_b(); +} diff --git a/test cases/unit/87 prelinking/file2.c b/test cases/unit/87 prelinking/file2.c new file mode 100644 index 0000000..ce3b115 --- /dev/null +++ b/test cases/unit/87 prelinking/file2.c @@ -0,0 +1,9 @@ +#include + +int round1_b() { + return round1_c(); +} + +int round2_b() { + return round2_c(); +} diff --git a/test cases/unit/87 prelinking/file3.c b/test cases/unit/87 prelinking/file3.c new file mode 100644 index 0000000..85052be --- /dev/null +++ b/test cases/unit/87 prelinking/file3.c @@ -0,0 +1,9 @@ +#include + +int round1_c() { + return round1_d(); +} + +int round2_c() { + return round2_d(); +} diff --git a/test cases/unit/87 prelinking/file4.c b/test cases/unit/87 prelinking/file4.c new file mode 100644 index 0000000..622364e --- /dev/null +++ b/test cases/unit/87 prelinking/file4.c @@ -0,0 +1,9 @@ +#include + +int round1_d() { + return round2_a(); +} + +int round2_d() { + return 42; +} diff --git a/test cases/unit/87 prelinking/main.c b/test cases/unit/87 prelinking/main.c new file mode 100644 index 0000000..09a2e5c --- /dev/null +++ b/test cases/unit/87 prelinking/main.c @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char **argv) { + if(public_func() != 42) { + printf("Something failed.\n"); + return 1; + } + return 0; +} diff --git a/test cases/unit/87 prelinking/meson.build b/test cases/unit/87 prelinking/meson.build new file mode 100644 index 0000000..3dbf88e --- /dev/null +++ b/test cases/unit/87 prelinking/meson.build @@ -0,0 +1,8 @@ +project('prelinking', 'c') + +liba = static_library('prelinked', 'file1.c', 'file2.c', 'file3.c', 'file4.c', + prelink: true) +exe = executable('testprog', 'main.c', + link_with: liba) +test('prelinked', exe) + \ No newline at end of file diff --git a/test cases/unit/87 prelinking/private_header.h b/test cases/unit/87 prelinking/private_header.h new file mode 100644 index 0000000..f24b621 --- /dev/null +++ b/test cases/unit/87 prelinking/private_header.h @@ -0,0 +1,11 @@ +#pragma once + +int round1_a(); +int round1_b(); +int round1_c(); +int round1_d(); + +int round2_a(); +int round2_b(); +int round2_c(); +int round2_d(); diff --git a/test cases/unit/87 prelinking/public_header.h b/test cases/unit/87 prelinking/public_header.h new file mode 100644 index 0000000..0cd6786 --- /dev/null +++ b/test cases/unit/87 prelinking/public_header.h @@ -0,0 +1,3 @@ +#pragma once + +int public_func(); -- cgit v1.1