| #! /usr/bin/env python3 |
| # Copyright 2020 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Integration tests for remote_link. |
| # |
| # Usage: |
| # |
| # Ensure that rewrapper, llvm-objdump, and llvm-dwarfdump are in your |
| # PATH. |
| # Then run: |
| # |
| # tools/clang/scripts/remote_link_integration_tests.py |
| # |
| # See also remote_link_unit_tests.py, which contains unit tests and |
| # instructions for generating coverage information. |
| |
| import remote_ld |
| import remote_link |
| |
| from io import StringIO |
| import os |
| import re |
| import shlex |
| import subprocess |
| import unittest |
| from unittest import mock |
| |
| from remote_link_test_utils import named_directory, working_directory |
| |
| # Path constants. |
| CHROMIUM_DIR = os.path.abspath( |
| os.path.join(os.path.dirname(__file__), '..', '..', '..')) |
| LLVM_BIN_DIR = os.path.join(CHROMIUM_DIR, 'third_party', 'llvm-build', |
| 'Release+Asserts', 'bin') |
| |
| |
| def _create_inputs(path): |
| """ |
| Creates input files under path. |
| """ |
| with open(os.path.join(path, 'main.cpp'), 'w') as f: |
| f.write('extern int foo();\n' |
| 'int main(int argc, char *argv[]) {\n return foo();\n}\n') |
| with open(os.path.join(path, 'foo.cpp'), 'w') as f: |
| f.write('int foo() {\n return 12;\n}\n') |
| with open(os.path.join(path, 'bar.cpp'), 'w') as f: |
| f.write('int bar() {\n return 9;\n}\n') |
| |
| |
| def _lto_args(generate_bitcode): |
| """ |
| Returns list of arguments to clang to generate bitcode or not. |
| """ |
| if generate_bitcode: |
| return ['-flto=thin'] |
| else: |
| return [] |
| |
| |
| class RemoteLinkUnixAllowMain(remote_ld.RemoteLinkUnix): |
| """ |
| Same as remote_ld.RemoteLinkUnix, but has "main" on the allow list. |
| """ |
| |
| def __init__(self, *args, **kwargs): |
| super(RemoteLinkUnixAllowMain, self).__init__(*args, **kwargs) |
| self.ALLOWLIST = {'main'} |
| |
| |
| class RemoteLinkWindowsAllowMain(remote_link.RemoteLinkWindows): |
| """ |
| Same as remote_ld.RemoteLinkWindows, but has "main" on the allow list. |
| """ |
| |
| def __init__(self, *args, **kwargs): |
| super(RemoteLinkWindowsAllowMain, self).__init__(*args, **kwargs) |
| self.ALLOWLIST = {'main.exe'} |
| |
| |
| class RemoteLinkIntegrationTest(unittest.TestCase): |
| def clangcl(self): |
| return os.path.join(LLVM_BIN_DIR, 'clang-cl' + remote_link.exe_suffix()) |
| |
| def lld_link(self): |
| return os.path.join(LLVM_BIN_DIR, 'lld-link' + remote_link.exe_suffix()) |
| |
| def llvmar(self): |
| return os.path.join(LLVM_BIN_DIR, 'llvm-ar' + remote_link.exe_suffix()) |
| |
| def test_distributed_lto_common_objs(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| os.makedirs('obj') |
| subprocess.check_call([ |
| self.clangcl(), '-c', '-Os', '-flto=thin', 'main.cpp', |
| '-Foobj/main.obj' |
| ]) |
| subprocess.check_call([ |
| self.clangcl(), '-c', '-Os', '-flto=thin', 'foo.cpp', '-Foobj/foo.obj' |
| ]) |
| subprocess.check_call([ |
| self.clangcl(), '-c', '-Os', '-flto=thin', 'bar.cpp', '-Foobj/bar.obj' |
| ]) |
| subprocess.check_call([ |
| self.llvmar(), 'crsT', 'obj/foobar.lib', 'obj/bar.obj', 'obj/foo.obj' |
| ]) |
| with open('main.rsp', 'w') as f: |
| f.write('obj/main.obj\n' 'obj/foobar.lib\n') |
| with open('my_reclient.sh', 'w') as f: |
| f.write('#! /bin/sh\n\nrewrapper "$@"\n') |
| os.chmod('my_reclient.sh', 0o755) |
| rc = remote_link.RemoteLinkWindows().main([ |
| 'remote_link.py', '--wrapper', './my_reclient.sh', '--ar-path', |
| self.llvmar(), '--', |
| self.lld_link(), '-nodefaultlib', '-entry:main', '-out:main.exe', |
| '@main.rsp' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # Check codegen parameters. |
| with open(os.path.join(d, 'lto.main.exe', 'build.ninja')) as f: |
| buildrules = f.read() |
| codegen_match = re.search('^rule codegen\\b.*?^[^ ]', buildrules, |
| re.MULTILINE | re.DOTALL) |
| self.assertIsNotNone(codegen_match) |
| codegen_text = codegen_match.group(0) |
| self.assertIn('my_reclient.sh', codegen_text) |
| self.assertNotIn('-flto', codegen_text) |
| self.assertIn('build common_objs/obj/main.obj.stamp : codegen ', |
| buildrules) |
| self.assertIn('build common_objs/obj/foo.obj.stamp : codegen ', |
| buildrules) |
| self.assertIn(' index = common_objs/empty.thinlto.bc', buildrules) |
| link_match = re.search('^build main.exe : native-link\\b.*?^[^ ]', |
| buildrules, re.MULTILINE | re.DOTALL) |
| self.assertIsNotNone(link_match) |
| link_text = link_match.group(0) |
| self.assertNotIn('main.exe.split.obj', link_text) |
| # Check that main does not call foo. |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main.exe']) |
| # There are no symbols in the disassembly, but we're expecting two |
| # functions, one of which calls the other. |
| self.assertTrue(b'call' in disasm or b'jmp' in disasm) |
| |
| def test_distributed_lto_allowlist(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| os.makedirs('obj') |
| subprocess.check_call([ |
| self.clangcl(), '-c', '-Os', '-flto=thin', '-m32', 'main.cpp', |
| '-Foobj/main.obj' |
| ]) |
| subprocess.check_call([ |
| self.clangcl(), '-c', '-Os', '-flto=thin', '-m32', 'foo.cpp', |
| '-Foobj/foo.obj' |
| ]) |
| subprocess.check_call([ |
| self.clangcl(), '-c', '-Os', '-flto=thin', '-m32', 'bar.cpp', |
| '-Foobj/bar.obj' |
| ]) |
| subprocess.check_call([ |
| self.llvmar(), 'crsT', 'obj/foobar.lib', 'obj/bar.obj', 'obj/foo.obj' |
| ]) |
| with open('main.rsp', 'w') as f: |
| f.write('obj/main.obj\n' 'obj/foobar.lib\n') |
| rc = RemoteLinkWindowsAllowMain().main([ |
| 'remote_link.py', '--wrapper', 'rewrapper', '--ar-path', |
| self.llvmar(), '--', |
| self.lld_link(), '-nodefaultlib', '-entry:main', '-machine:X86', |
| '-opt:lldlto=2', '-mllvm:-import-instr-limit=10', '-out:main.exe', |
| '@main.rsp' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # Check codegen parameters. |
| with open(os.path.join(d, 'lto.main.exe', 'build.ninja')) as f: |
| buildrules = f.read() |
| codegen_match = re.search('^rule codegen\\b.*?^[^ ]', buildrules, |
| re.MULTILINE | re.DOTALL) |
| self.assertIsNotNone(codegen_match) |
| codegen_text = codegen_match.group(0) |
| self.assertIn('rewrapper', codegen_text) |
| self.assertIn('-m32', codegen_text) |
| self.assertIn('-mllvm -import-instr-limit=10', codegen_text) |
| self.assertNotIn('-flto', codegen_text) |
| self.assertIn('build lto.main.exe/obj/main.obj.stamp : codegen ', |
| buildrules) |
| self.assertIn('build lto.main.exe/obj/foo.obj.stamp : codegen ', |
| buildrules) |
| link_match = re.search('^build main.exe : native-link\\b.*?^[^ ]', |
| buildrules, re.MULTILINE | re.DOTALL) |
| self.assertIsNotNone(link_match) |
| link_text = link_match.group(0) |
| self.assertIn('main.exe.split.obj', link_text) |
| # Check that main does not call foo. |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main.exe']) |
| # There are no symbols in the disassembly, but we're expecting a single |
| # function, with no calls or jmps. |
| self.assertNotIn(b'jmp', disasm) |
| self.assertNotIn(b'call', disasm) |
| |
| def test_override_allowlist(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| os.makedirs('obj') |
| subprocess.check_call([ |
| self.clangcl(), '-c', '-O2', '-flto=thin', 'main.cpp', |
| '-Foobj/main.obj' |
| ]) |
| subprocess.check_call([ |
| self.clangcl(), '-c', '-O2', '-flto=thin', 'foo.cpp', '-Foobj/foo.obj' |
| ]) |
| rc = remote_link.RemoteLinkWindows().main([ |
| 'remote_link.py', '--generate', '--allowlist', '--ar-path', |
| self.llvmar(), '--', |
| self.lld_link(), '-nodefaultlib', '-entry:main', '-opt:lldlto=2', |
| '-out:main.exe', 'obj/main.obj', 'obj/foo.obj' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # Check that we have rules for main and foo, and that they are |
| # not common objects. |
| with open(os.path.join(d, 'lto.main.exe', 'build.ninja')) as f: |
| buildrules = f.read() |
| codegen_match = re.search(r'^rule codegen\b.*?^[^ ]', buildrules, |
| re.MULTILINE | re.DOTALL) |
| self.assertIsNotNone(codegen_match) |
| codegen_text = codegen_match.group(0) |
| self.assertNotIn('-flto', codegen_text) |
| self.assertIn('build lto.main.exe/obj/main.obj.stamp : codegen ', |
| buildrules) |
| self.assertIn('build lto.main.exe/obj/foo.obj.stamp : codegen ', |
| buildrules) |
| link_match = re.search(r'^build main.exe : native-link\b.*?^[^ ]', |
| buildrules, re.MULTILINE | re.DOTALL) |
| self.assertIsNotNone(link_match) |
| |
| |
| class RemoteLdIntegrationTest(unittest.TestCase): |
| def clangxx(self): |
| return os.path.join(LLVM_BIN_DIR, 'clang++' + remote_link.exe_suffix()) |
| |
| def llvmar(self): |
| return os.path.join(LLVM_BIN_DIR, 'llvm-ar' + remote_link.exe_suffix()) |
| |
| def test_nonlto(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', 'main.cpp', '-o', 'main.o']) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', 'foo.cpp', '-o', 'foo.o']) |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '--wrapper', 'rewrapper', '--ar-path', |
| self.llvmar(), '--', |
| self.clangxx(), '-fuse-ld=lld', 'main.o', 'foo.o', '-o', 'main' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # lto.main directory should not be present. |
| self.assertFalse(os.path.exists(os.path.join(d, 'lto.main'))) |
| # Check that main calls foo. |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main']) |
| main_idx = disasm.index(b' <main>:\n') |
| after_main_idx = disasm.index(b'\n\n', main_idx) |
| main_disasm = disasm[main_idx:after_main_idx] |
| self.assertIn(b'foo', main_disasm) |
| |
| def test_fallback_lto(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-Os', '-flto=thin', 'main.cpp', '-o', 'main.o' |
| ]) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', '-flto=thin', 'foo.cpp', '-o', 'foo.o']) |
| rc = remote_ld.RemoteLinkUnix().main([ |
| 'remote_ld.py', '--wrapper', 'rewrapper', '--ar-path', |
| self.llvmar(), '--', |
| self.clangxx(), '-fuse-ld=lld', '-flto=thin', 'main.o', 'foo.o', '-o', |
| 'main' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # lto.main directory should not be present. |
| self.assertFalse(os.path.exists(os.path.join(d, 'lto.main'))) |
| # Check that main does not call foo. |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main']) |
| main_idx = disasm.index(b' <main>:\n') |
| after_main_idx = disasm.index(b'\n\n', main_idx) |
| main_disasm = disasm[main_idx:after_main_idx] |
| self.assertNotIn(b'foo', main_disasm) |
| |
| def test_distributed_lto(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-Os', '-flto=thin', 'main.cpp', '-o', 'main.o' |
| ]) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', '-flto=thin', 'foo.cpp', '-o', 'foo.o']) |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '-j', '16', '--ar-path', |
| self.llvmar(), '--', |
| self.clangxx(), '-fuse-ld=lld', '-flto=thin', 'main.o', 'foo.o', '-o', |
| 'main' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # build.ninja file should have rewrapper invocations in it. |
| with open(os.path.join(d, 'lto.main', 'build.ninja')) as f: |
| buildrules = f.read() |
| self.assertIn('rewrapper ', buildrules) |
| self.assertIn('build lto.main/main.o.stamp : codegen ', buildrules) |
| self.assertIn('build lto.main/foo.o.stamp : codegen ', buildrules) |
| # Check that main does not call foo. |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main']) |
| main_idx = disasm.index(b' <main>:\n') |
| after_main_idx = disasm.index(b'\n\n', main_idx) |
| main_disasm = disasm[main_idx:after_main_idx] |
| self.assertNotIn(b'foo', main_disasm) |
| |
| def test_distributed_lto_thin_archive_same_dir(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-Os', '-flto=thin', 'main.cpp', '-o', 'main.o' |
| ]) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', '-flto=thin', 'foo.cpp', '-o', 'foo.o']) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', '-flto=thin', 'bar.cpp', '-o', 'bar.o']) |
| subprocess.check_call( |
| [self.llvmar(), 'crsT', 'libfoobar.a', 'bar.o', 'foo.o']) |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '--ar-path', |
| self.llvmar(), '--', |
| self.clangxx(), '-fuse-ld=lld', '-flto=thin', 'main.o', 'libfoobar.a', |
| '-o', 'main' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # build.ninja file should have rewrapper invocations in it. |
| with open(os.path.join(d, 'lto.main', 'build.ninja')) as f: |
| buildrules = f.read() |
| self.assertIn('rewrapper ', buildrules) |
| self.assertIn('build lto.main/main.o.stamp : codegen ', buildrules) |
| self.assertIn('build lto.main/foo.o.stamp : codegen ', buildrules) |
| # Check that main does not call foo. |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main']) |
| main_idx = disasm.index(b' <main>:\n') |
| after_main_idx = disasm.index(b'\n\n', main_idx) |
| main_disasm = disasm[main_idx:after_main_idx] |
| self.assertNotIn(b'foo', main_disasm) |
| |
| def test_distributed_lto_thin_archive_subdir(self): |
| self.run_archive_test(bitcode_archive=True, |
| bitcode_main=True, |
| thin_archive=True) |
| |
| def test_distributed_machine_code_thin_archive_bitcode_main_subdir(self): |
| self.run_archive_test(bitcode_archive=False, |
| bitcode_main=True, |
| thin_archive=True) |
| |
| def test_distributed_machine_code_thin_archive_subdir(self): |
| self.run_archive_test(bitcode_archive=False, |
| bitcode_main=False, |
| thin_archive=True) |
| |
| def test_distributed_bitcode_thick_archive_subdir(self): |
| self.run_archive_test(bitcode_archive=True, |
| bitcode_main=True, |
| thin_archive=False) |
| |
| def test_distributed_machine_code_thick_archive_subdir(self): |
| self.run_archive_test(bitcode_archive=False, |
| bitcode_main=False, |
| thin_archive=False) |
| |
| def test_distributed_machine_code_thick_archive_bitcode_main_subdir(self): |
| self.run_archive_test(bitcode_archive=False, |
| bitcode_main=True, |
| thin_archive=False) |
| |
| def run_archive_test(self, bitcode_archive, bitcode_main, thin_archive): |
| """ |
| Runs a test to ensure correct remote linking handling of |
| an archive. |
| Arguments: |
| bitcode_archive: whether the archive should contain bitcode (true) |
| or machine code (false) |
| bitcode_main: whether the main object, outside the archive, should |
| contain bitcode (true) or machine code (false) |
| thin_archive: whether to create a thin archive instead of a regular archive. |
| """ |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| os.makedirs('obj') |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', 'main.cpp', '-o', 'obj/main.o'] + |
| _lto_args(bitcode_main)) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', 'foo.cpp', '-o', 'obj/foo.o'] + |
| _lto_args(bitcode_archive)) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', 'bar.cpp', '-o', 'obj/bar.o'] + |
| _lto_args(bitcode_archive)) |
| archive_creation_arg = 'crs' |
| if thin_archive: |
| archive_creation_arg = 'crsT' |
| subprocess.check_call([ |
| self.llvmar(), archive_creation_arg, 'obj/libfoobar.a', 'obj/bar.o', |
| 'obj/foo.o' |
| ]) |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '--ar-path', |
| self.llvmar(), '--', |
| self.clangxx(), '-fuse-ld=lld', '-flto=thin', 'obj/main.o', |
| 'obj/libfoobar.a', '-o', 'main' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| if bitcode_main or bitcode_archive: |
| # build.ninja file should have rewrapper invocations in it. |
| with open(os.path.join(d, 'lto.main', 'build.ninja')) as f: |
| buildrules = f.read() |
| self.assertIn('rewrapper ', buildrules) |
| if bitcode_main: |
| self.assertIn('build lto.main/obj/main.o.stamp : codegen ', |
| buildrules) |
| if bitcode_archive: |
| if thin_archive: |
| self.assertIn('build lto.main/obj/foo.o.stamp : codegen ', |
| buildrules) |
| else: |
| self.assertIn( |
| 'build lto.main/expanded_archives/obj/libfoobar.a/' + |
| 'foo.o.stamp : codegen ', buildrules) |
| # Check that main does not call foo. |
| if bitcode_archive: |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main']) |
| main_idx = disasm.index(b' <main>:\n') |
| after_main_idx = disasm.index(b'\n\n', main_idx) |
| main_disasm = disasm[main_idx:after_main_idx] |
| self.assertNotIn(b'foo', main_disasm) |
| |
| def test_debug_params(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| os.makedirs('obj') |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-g', '-gsplit-dwarf', '-flto=thin', 'main.cpp', |
| '-o', 'obj/main.o' |
| ]) |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-g', '-gsplit-dwarf', '-flto=thin', 'foo.cpp', |
| '-o', 'obj/foo.o' |
| ]) |
| with open('main.rsp', 'w') as f: |
| f.write('obj/main.o\n' 'obj/foo.o\n') |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '--ar-path', |
| self.llvmar(), '--', |
| self.clangxx(), '-fuse-ld=lld', '-flto=thin', '-g', '-gsplit-dwarf', |
| '-Wl,--lto-O2', '-o', 'main', '@main.rsp' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # Check debug info present, refers to .dwo file, and does not |
| # contain full debug info for foo.cpp. |
| dbginfo = subprocess.check_output( |
| ['llvm-dwarfdump', '-debug-info', |
| 'main']).decode('utf-8', 'backslashreplace') |
| self.assertRegexpMatches(dbginfo, '\\bDW_AT_GNU_dwo_name\\b.*\\.dwo"') |
| self.assertNotRegexpMatches(dbginfo, '\\bDW_AT_name\\b.*foo\\.cpp"') |
| |
| def test_distributed_lto_params(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| os.makedirs('obj') |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-Os', '-flto=thin', '-m32', '-fsplit-lto-unit', |
| '-fwhole-program-vtables', 'main.cpp', '-o', 'obj/main.o' |
| ]) |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-Os', '-flto=thin', '-m32', '-fsplit-lto-unit', |
| '-fwhole-program-vtables', 'foo.cpp', '-o', 'obj/foo.o' |
| ]) |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-Os', '-flto=thin', '-m32', '-fsplit-lto-unit', |
| '-fwhole-program-vtables', 'bar.cpp', '-o', 'obj/bar.o' |
| ]) |
| subprocess.check_call( |
| [self.llvmar(), 'crsT', 'obj/libfoobar.a', 'obj/bar.o', 'obj/foo.o']) |
| with open('main.rsp', 'w') as f: |
| f.write('-fsplit-lto-unit\n' |
| '-fwhole-program-vtables\n' |
| 'obj/main.o\n' |
| 'obj/libfoobar.a\n') |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '--ar-path', |
| self.llvmar(), '--', |
| self.clangxx(), '-fuse-ld=lld', '-flto=thin', '-m32', '-Wl,-mllvm', |
| '-Wl,-generate-type-units', '-Wl,--lto-O2', '-o', 'main', |
| '-Wl,--start-group', '@main.rsp', '-Wl,--end-group' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # Check codegen parameters. |
| with open(os.path.join(d, 'lto.main', 'build.ninja')) as f: |
| buildrules = f.read() |
| codegen_match = re.search('^rule codegen\\b.*?^[^ ]', buildrules, |
| re.MULTILINE | re.DOTALL) |
| self.assertIsNotNone(codegen_match) |
| codegen_text = codegen_match.group(0) |
| self.assertIn('rewrapper', codegen_text) |
| self.assertIn('-m32', codegen_text) |
| self.assertIn('-mllvm -generate-type-units', codegen_text) |
| self.assertNotIn('-flto', codegen_text) |
| self.assertIn('build lto.main/obj/main.o.stamp : codegen ', buildrules) |
| self.assertIn('build lto.main/obj/foo.o.stamp : codegen ', buildrules) |
| link_match = re.search('^build main : native-link\\b.*?^[^ ]', |
| buildrules, re.MULTILINE | re.DOTALL) |
| self.assertIsNotNone(link_match) |
| link_text = link_match.group(0) |
| self.assertIn('main.split.o', link_text) |
| # Check that main does not call foo. |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main']) |
| main_idx = disasm.index(b' <main>:\n') |
| after_main_idx = disasm.index(b'\n\n', main_idx) |
| main_disasm = disasm[main_idx:after_main_idx] |
| self.assertNotIn(b'foo', main_disasm) |
| |
| def test_no_rewrapper(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-Os', '-flto=thin', 'main.cpp', '-o', 'main.o' |
| ]) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', '-flto=thin', 'foo.cpp', '-o', 'foo.o']) |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '--ar-path', |
| self.llvmar(), '--no-wrapper', '-j', '16', '--', |
| self.clangxx(), '-fuse-ld=lld', '-flto=thin', 'main.o', 'foo.o', '-o', |
| 'main' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # build.ninja file should not have rewrapper invocations in it. |
| with open(os.path.join(d, 'lto.main', 'build.ninja')) as f: |
| buildrules = f.read() |
| self.assertNotIn('rewrapper ', buildrules) |
| self.assertIn('build lto.main/main.o.stamp : codegen ', buildrules) |
| self.assertIn('build lto.main/foo.o.stamp : codegen ', buildrules) |
| # Check that main does not call foo. |
| disasm = subprocess.check_output(['llvm-objdump', '-d', 'main']) |
| main_idx = disasm.index(b' <main>:\n') |
| after_main_idx = disasm.index(b'\n\n', main_idx) |
| main_disasm = disasm[main_idx:after_main_idx] |
| self.assertNotIn(b'foo', main_disasm) |
| |
| def test_generate_no_codegen(self): |
| with named_directory() as d, working_directory(d): |
| with open('main.o', 'wb') as f: |
| f.write(b'\7fELF') |
| with mock.patch('sys.stderr', new_callable=StringIO) as stderr: |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '--ar-path', |
| self.llvmar(), '--generate', '--', |
| self.clangxx(), 'main.o', '-o', 'main' |
| ]) |
| self.assertEqual(rc, 5) |
| self.assertIn('no ninja file generated.\n', stderr.getvalue()) |
| |
| def test_generate(self): |
| with named_directory() as d, working_directory(d): |
| with open('main.o', 'wb') as f: |
| f.write(b'BC\xc0\xde') |
| with mock.patch('sys.stderr', new_callable=StringIO) as stderr: |
| rc = RemoteLinkUnixAllowMain().main([ |
| 'remote_ld.py', '--ar-path', |
| self.llvmar(), '--generate', '--', |
| self.clangxx(), 'main.o', '-o', 'main' |
| ]) |
| self.assertEqual(rc, 0) |
| m = re.search('ninja file (.*)', stderr.getvalue()) |
| self.assertIsNotNone(m) |
| path = shlex.split(m.group(1))[0] |
| self.assertTrue(os.path.exists(path)) |
| content = open(path).read() |
| self.assertRegex( |
| content, |
| re.compile('^build [^:]+/main\\.o\\.stamp : codegen ', |
| re.MULTILINE)) |
| |
| def test_override_allowlist(self): |
| with named_directory() as d, working_directory(d): |
| _create_inputs(d) |
| subprocess.check_call([ |
| self.clangxx(), '-c', '-Os', '-flto=thin', 'main.cpp', '-o', 'main.o' |
| ]) |
| subprocess.check_call( |
| [self.clangxx(), '-c', '-Os', '-flto=thin', 'foo.cpp', '-o', 'foo.o']) |
| rc = remote_ld.RemoteLinkUnix().main([ |
| 'remote_ld.py', '--ar-path', |
| self.llvmar(), '--generate', '--allowlist', '--', |
| self.clangxx(), '-fuse-ld=lld', '-flto=thin', 'main.o', 'foo.o', '-o', |
| 'main' |
| ]) |
| # Should succeed. |
| self.assertEqual(rc, 0) |
| # build.ninja file should have rules for main and foo. |
| ninjafile = os.path.join(d, 'lto.main', 'build.ninja') |
| self.assertTrue(os.path.exists(ninjafile)) |
| with open(ninjafile) as f: |
| buildrules = f.read() |
| self.assertIn('build lto.main/main.o.stamp : codegen ', buildrules) |
| self.assertIn('build lto.main/foo.o.stamp : codegen ', buildrules) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |