Index: CMakeLists.txt =================================================================== --- CMakeLists.txt +++ CMakeLists.txt @@ -96,9 +96,12 @@ # Declare libc++ configuration variables. # They are intended for use as follows: +# LIBCXX_TARGET_FLAGS: Compile and link flags that affect the output target or +# arch. ex: -m32 -target # LIBCXX_CXX_FLAGS: General flags for both the compiler and linker. # LIBCXX_COMPILE_FLAGS: Compile only flags. # LIBCXX_LINK_FLAGS: Linker only flags. +set(LIBCXX_TARGET_FLAGS "") set(LIBCXX_CXX_FLAGS "") set(LIBCXX_COMPILE_FLAGS "") set(LIBCXX_LINK_FLAGS "") @@ -213,7 +216,7 @@ if (CMAKE_SIZEOF_VOID_P EQUAL 8 AND NOT WIN32) if (LIBCXX_BUILD_32_BITS) message(STATUS "Building 32 bits executables and libraries.") - list(APPEND LIBCXX_CXX_FLAGS "-m32") + list(APPEND LIBCXX_TARGET_FLAGS "-m32") endif() elseif(LIBCXX_BUILD_32_BITS) message(FATAL_ERROR "LIBCXX_BUILD_32_BITS=ON is not supported on this platform.") @@ -268,14 +271,29 @@ endif() endif() -append_if(LIBCXX_CXX_FLAGS LIBCXX_TARGET_TRIPLE +append_if(LIBCXX_TARGET_FLAGS LIBCXX_TARGET_TRIPLE "-target ${LIBCXX_TARGET_TRIPLE}") -append_if(LIBCXX_CXX_FLAGS LIBCXX_SYSROOT "--sysroot ${LIBCXX_SYSROOT}") -append_if(LIBCXX_CXX_FLAGS LIBCXX_GCC_TOOLCHAIN + +append_if(LIBCXX_TARGET_FLAGS LIBCXX_SYSROOT "--sysroot ${LIBCXX_SYSROOT}") +append_if(LIBCXX_TARGET_FLAGS LIBCXX_GCC_TOOLCHAIN "-gcc-toolchain ${LIBCXX_GCC_TOOLCHAIN}") +# We need to probe and manually link the sanitizer libraries on OS X. +if (APPLE AND LLVM_USE_SANITIZER) + set(LIBCXX_COMPILER_RT_OPTION "${LLVM_USE_SANITIZER}") +endif() + +# Manually link the required sanitizer libraries. +if (DEFINED LIBCXX_COMPILER_RT_OPTION) + include(GetCompilerRTOptions) + get_compiler_rt_option(LIBCXX_COMPILER_RT_LINK_LIBRARIES + "${LIBCXX_COMPILER_RT_OPTION}" + "${LIBCXX_TARGET_FLAGS}") +endif() + string(REPLACE ";" " " LIBCXX_CXX_FLAGS "${LIBCXX_CXX_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LIBCXX_CXX_FLAGS}") +string(REPLACE ";" " " LIBCXX_TARGET_FLAGS "${LIBCXX_TARGET_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LIBCXX_CXX_FLAGS} ${LIBCXX_TARGET_FLAGS}") #=============================================================================== # Setup Source Code Index: cmake/Modules/GetCompilerRTOptions.cmake =================================================================== --- /dev/null +++ cmake/Modules/GetCompilerRTOptions.cmake @@ -0,0 +1,49 @@ +# An internal method to setup the python enviroment. +macro(_setup_python) + if (LIT_EXECUTABLE) + get_filename_component(LIT_MODULE_PATH ${LIT_EXECUTABLE} DIRECTORY) + else() + set(LIT_MODULE_PATH "${CMAKE_SOURCE_DIR}/utils/lit") + endif() + if (NOT IS_DIRECTORY ${LIT_MODULE_PATH}) + message(FATAL_ERROR "Failed to find the LIT module.") + endif() + set(LIBCXX_PYTHONPATH "${LIT_MODULE_PATH}:${LIBCXX_SOURCE_DIR}/test:$ENV{PYTHONPATH}") + set(ENV{PYTHONPATH} "${LIBCXX_PYTHONPATH}") + include(FindPythonInterp) + if(NOT PYTHONINTERP_FOUND) + message(FATAL_ERROR "Failed to find python executable") + endif() +endmacro() + +#=============================================================================== +# Get CompilerRT link flags +#=============================================================================== +# +# get_compiler_rt_option: Get link flags for a given compiler-rt library. +# +# Parameters: +# output_var: The variable to write the list of flags to. +# opt: The compiler-rt configuration to get the flags for. It can be one of +# Profile, Address, Memory, MemoryWithOrigins, Undefined, Thread. +# extra_flags: A list of extra compiler flags used to help find the +# compiler_rt libraries. +function(get_compiler_rt_option output_var opt extra_flags) + if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + message(FATAL_ERROR + "GetCompilerRTOptions can only be used with a Clang compiler") + endif() + _setup_python() + separate_arguments(extra_flags) + foreach(e ${extra_flags}) + list(APPEND EXTRA_FLAGS "--flag;${e}") + endforeach() + execute_process(COMMAND ${PYTHON_EXECUTABLE} ${LIBCXX_SOURCE_DIR}/test/libcxx/clang_utils.py --cxx ${CMAKE_CXX_COMPILER} ${EXTRA_FLAGS} ${opt} + RESULT_VARIABLE RES_VAR + OUTPUT_VARIABLE OUTPUT_VAR) + if (NOT RES_VAR EQUAL 0) + message(FATAL_ERROR "get_compiler_rt_option failed to get link options for ${opt}") + endif() + string(STRIP "${OUTPUT_VAR}" OUTPUT_VAR) + set(${output_var} "${OUTPUT_VAR}" PARENT_SCOPE) +endfunction() Index: lib/CMakeLists.txt =================================================================== --- lib/CMakeLists.txt +++ lib/CMakeLists.txt @@ -50,6 +50,7 @@ if (DEFINED LIBCXX_CXX_ABI_LIBRARY_PATH) target_link_libraries(cxx "-L${LIBCXX_CXX_ABI_LIBRARY_PATH}") endif() +target_link_libraries(cxx ${LIBCXX_COMPILER_RT_LINK_LIBRARIES}) target_link_libraries(cxx ${libraries}) # Setup flags. Index: test/libcxx/clang_utils.py =================================================================== --- /dev/null +++ test/libcxx/clang_utils.py @@ -0,0 +1,357 @@ +""" +clang_utils - Utilities for linking with the clang compiler. +""" + +import os +import libcxx.compiler + + +class BasicClangLinkDriver(object): + """ + BasicClangLinkDriver - A linker driver that mimics how clang links + compiler-rt + """ + def __init__(self, cxx): + self.cxx = cxx + if cxx.type is None or 'clang' not in cxx.type: + raise RuntimeError('Compiler is not a clang variant') + search_paths = self.cxx.getLibrarySearchPaths() + if not search_paths: + raise RuntimeError('Failed to get search paths') + self.clang_resource_dir = search_paths[0] + if not os.path.isdir(self.clang_resource_dir): + raise RuntimeError('Clang resource directory does not exist') + self.target_info = self.cxx.getTargetInfo() + sys = self.target_info.sys + if sys.startswith('darwin'): + self.target_info.sys = 'darwin' + self.clang_library_dir = os.path.join( + self.clang_resource_dir, 'lib', self.target_info.sys) + if not os.path.isdir(self.clang_library_dir): + raise RuntimeError('clang library path does not exist: %s' % + self.clang_library_dir) + + def getClangResourceDir(self): + """ + Return clangs root resource dir. + + If clang is /path/to/bin/clang++ then the resource dir is usually + /path/to/bin/../lib/clang/../ + """ + return self.clang_resource_dir + + def getClangLibraryDir(self): + """ + Return clangs library dir. This directory contains the compiler-rt + libraries. + + If clang is /path/to/bin/clang++ then the library dir is usually + /path/to/bin/../lib/clang/../lib/ + """ + return self.clang_library_dir + + def getStaticCompilerRTLibrary(self, suffix, required=False): + """ + Return the full path to a static clang_rt library or None if it doesn't + exist. + + If required is True then the function will throw if the library is not + found. + """ + libname = 'libclang_rt.%s.a' % suffix + libpath = os.path.join(self.clang_library_dir, libname) + if os.path.isfile(libpath): + return libpath + elif required: + raise RuntimeError('Required library %s not found at %s' % + (libname, libpath)) + else: + return None + + def getStaticCompilerRTLibraryWithArch(self, name, required=False): + """ + Return the full path to a static architecture specific clang_rt library + or None if it doesn't exist. + + If required is True then the function will throw if the library is not + found. + """ + return self.getStaticCompilerRTLibrary( + '%s-%s' % (name, self.target_info.arch), required=required) + + def getSharedCompilerRTLibrary(self, suffix, required=False): + """ + Return the full path to a shared clang_rt library or None if it doesn't + exist. + + If required is True then the function will throw if the library is not + found. + """ + libname = 'libclang_rt.%s' % suffix + if self.target_info.sys == 'darwin': + libname += '.dylib' + else: + libname += '.so' + libpath = os.path.join(self.clang_library_dir, libname) + if os.path.isfile(libpath): + return libpath + elif required: + raise RuntimeError('Required library %s not found at %s' % + (libname, libpath)) + else: + return None + + def getSharedCompilerRTLibraryWithArch(self, name, required=False): + """ + Return the full path to a shared architecture specific clang_rt library + or None if it doesn't exist. + + If required is True then the function will throw if the library is not + found. + """ + return self.getSharedCompilerRTLibrary( + '%s-%s' % (name, self.target_info.arch), required=required) + + def getCompilerRTLibrary(self, name): + """ + Return the full path to a shared or static clang_rt library. Throw if + the library doesn't exist. + + This function will try looking for the shared library first and if that + is not found will search for the static version. + """ + ret = self.getSharedCompilerRTLibrary(name) + if ret is None: + ret = self.getStaticCompilerRTLibrary(name) + if ret is None: + raise RuntimeError('Failed to find library for name: %s' % name) + return ret + + def getCompilerRTLibraryWithArch(self, name): + """ + Return the full path to a shared or static architecture specific + clang_rt library. Throw if the library doesn't exist. + + This function will try looking for the shared library first and if that + is not found will search for the static version. + """ + ret = self.getSharedCompilerRTLibraryWithArch(name) + if ret is None: + ret = self.getStaticCompilerRTLibraryWithArch(name) + if ret is None: + raise RuntimeError('Failed to find library for name: %s' % name) + return ret + + def getProfileRTLinkOptions(self): + """ + Return a list of link options for ProfileRt + """ + raise NotImplementedError("abstract method for %s" % type(self)) + + def getASanLinkOptions(self): + """ + Return a list of link options for ASAN + """ + raise NotImplementedError("abstract method for %s" % type(self)) + + def getMSanLinkOptions(self): + """ + Return a list of link options for MSAN + """ + raise NotImplementedError("abstract method for %s" % type(self)) + + def getUBSanLinkOptions(self): + """ + Return a list of link options for UBSAN + """ + raise NotImplementedError("abstract method for %s" % type(self)) + + def getTSanLinkOptions(self): + """ + Return a list of link options for TSAN + """ + raise NotImplementedError("abstract method for %s" % type(self)) + + +class DarwinClangLinkDriver(BasicClangLinkDriver): + """ + The implementation of BasicClangLinkDriver for stock clang on OS X. + """ + def __init__(self, cxx): + super(DarwinClangLinkDriver, self).__init__(cxx) + + def getProfileRTLinkOptions(self): + return [ + self.getCompilerRTLibrary('profile_osx'), + ] + + def getASanLinkOptions(self): + return [ + self.getCompilerRTLibrary('asan_osx_dynamic') + ] + + def getMSanLinkOptions(self): + raise RuntimeError("MSan not supported") + + def getUBSanLinkOptions(self): + return [ + self.getCompilerRTLibrary('ubsan_osx') + ] + + def getTSanLinkOptions(self): + raise RuntimeError("TSan not supported") + + +class DarwinAppleClangLinkDriver(BasicClangLinkDriver): + """ + The implementation of BasicClangLinkDriver for apple clang on OS X. + """ + def __init__(self, cxx): + super(DarwinAppleClangLinkDriver, self).__init__(cxx) + + def getProfileRTLinkOptions(self): + return [ + self.getCompilerRTLibrary('profile_osx'), + '-lSystem', + self.getCompilerRTLibrary('osx') + ] + + def getASanLinkOptions(self): + return [ + self.getCompilerRTLibrary('asan_osx_dynamic'), + '-lSystem', + self.getCompilerRTLibrary('osx') + ] + + def getMSanLinkOptions(self): + raise RuntimeError("MSan not supported") + + def getUBSanLinkOptions(self): + return [ + self.getCompilerRTLibrary('ubsan_osx'), + '-lSystem', + self.getCompilerRTLibrary('osx') + ] + + def getTSanLinkOptions(self): + raise RuntimeError("TSan not supported") + + +class LinuxClangLinkDriver(BasicClangLinkDriver): + """ + The implementation of BasicClangLinkDriver for clang on linux. + """ + def __init__(self, cxx): + super(LinuxClangLinkDriver, self).__init__(cxx) + + def _getStaticLib(self, name): + return self.getStaticCompilerRTLibraryWithArch(name, required=True) + + def _getWholeStaticLib(self, name): + libpath = self._getStaticLib(name) + return ['-Wl,--whole-archive', libpath, '-Wl,--no-whole-archive'] + + def _getWholeStaticLibWithSyms(self, name): + libpath = self._getStaticLib(name) + return ['-Wl,--whole-archive', libpath, '-Wl,--no-whole-archive', + '-Wl,--dynamic-list=' + libpath + '.syms'] + + def getProfileRTLinkOptions(self): + return [ + self._getStaticLib('profile') + ] + + def getASanLinkOptions(self): + args = [] + args += self._getWholeStaticLibWithSyms('asan') + args += self._getWholeStaticLibWithSyms('asan_cxx') + return args + + def getMSanLinkOptions(self): + return self._getWholeStaticLibWithSyms('msan') + + def getUBSanLinkOptions(self): + args = [] + args += self._getWholeStaticLib('san') + args += self._getWholeStaticLibWithSyms('ubsan') + args += self._getWholeStaticLibWithSyms('ubsan_cxx') + return args + + def getTSanLinkOptions(self): + return self._getWholeStaticLibWithSyms('tsan') + + +def getClangLinkDriver(cxx): + """ + Return the implementation of the Clang linker driver for a giver platform + and compiler. + """ + if cxx.type is None: + raise RuntimeError('Compiler type could not be determined') + elif 'clang' not in cxx.type: + raise RuntimeError('Compiler %s is not clang' % cxx.type) + elif cxx.type == 'apple-clang': + return DarwinAppleClangLinkDriver(cxx) + else: # cxx.type == 'clang' + trip = cxx.getTriple() + if 'darwin' in trip: + return DarwinClangLinkDriver(cxx) + elif 'linux' in trip: + return LinuxClangLinkDriver(cxx) + else: + raise RuntimeError('Unsupported target: %s' % trip) + assert False + + +def main(): + """ + Print a semi-colon list of compiler-rt link flags based on the compiler + and arguments. + + clang-utils.py [--cxx compiler] [--flag cxx_flag]... Spec + options: + --cxx - The cxx compiler to use + --flag - A list of extra flags to configure with + Spec - One of Profile, Address, Memory, MemoryWithAddress, Undefined, + Thread. + """ + from optparse import OptionParser + parser = OptionParser("usage: %prog [options] {CompilerRT Spec}") + parser.add_option('-c', '--cxx', dest='compiler', + help='The compiler to use', + type=str, action='store', default='clang++') + parser.add_option("-f", "--flag", dest="extraFlags", + metavar="NAME=VAL", + help="Add 'NAME' = 'VAL' to the flags used", + type=str, action='append', default=[]) + (opts, args) = parser.parse_args() + cxx = libcxx.compiler.CXXCompiler(opts.compiler, flags=opts.extraFlags) + driver = getClangLinkDriver(cxx) + if len(args) != 1: + print('Invalid args: %s' % args) + exit(1) + spec = args[0] + info_specs = { + 'Profile': driver.getProfileRTLinkOptions, + 'Address': driver.getASanLinkOptions, + 'Memory': driver.getMSanLinkOptions, + 'MemoryWithAddress': driver.getMSanLinkOptions, + 'Undefined': driver.getUBSanLinkOptions, + 'Thread': driver.getUBSanLinkOptions + } + if spec not in info_specs.keys(): + print('%s is not a valid spec' % spec) + exit(1) + try: + out = ';'.join(info_specs[spec]()) + print(out) + except RuntimeError as e: + print('%s Exception thrown' % e) + exit(1) + except: + print('Unknown exception thrown') + exit(1) + +if __name__ == '__main__': + main() + exit(0) Index: test/libcxx/compiler.py =================================================================== --- test/libcxx/compiler.py +++ test/libcxx/compiler.py @@ -1,8 +1,17 @@ import os +import re import lit.util import libcxx.util +class TargetInfo: + def __init__(self, arch=None, sys=None, vendor=None, abi=None): + self.arch = arch + self.sys = sys + self.vendor = vendor + self.abi = abi + + class CXXCompiler(object): def __init__(self, path, flags=None, compile_flags=None, link_flags=None, use_ccache=False): @@ -133,4 +142,41 @@ def getTriple(self): cmd = [self.path] + self.flags + ['-dumpmachine'] - return lit.util.capture(cmd).strip() + out, err, rc = lit.util.executeCommand(cmd) + if rc != 0: + raise Exception() + return out.strip() + + def getTargetInfo(self): + triple = self.getTriple() + parts = triple.split('-') + assert len(parts) == 3 or len(parts) == 4 + info = TargetInfo(parts[0]) + if len(parts) == 3: + if parts[1] in ['apple'] or parts[2] in ['linux']: + info.vendor = parts[1] + info.sys = parts[2] + else: + info.vendor = 'unknown' + info.sys = parts[1] + info.abi = parts[2] + if info.sys == 'linux': + info.abi = 'gnu' + else: + info.vendor = parts[1] + info.sys = parts[2] + info.abi = parts[3] + return info + + kSearchDirsRe = re.compile('libraries: =([^\n]*)\n') + + def getLibrarySearchPaths(self): + cmd = [self.path] + self.flags + ['-print-search-dirs'] + out, err, rc = lit.util.executeCommand(cmd) + if rc != 0: + raise Exception() + match = CXXCompiler.kSearchDirsRe.search(out) + assert match is not None + dir_list = match.group(1).strip().split(':') + assert len(dir_list) != 0 + return dir_list Index: test/libcxx/test/config.py =================================================================== --- test/libcxx/test/config.py +++ test/libcxx/test/config.py @@ -11,6 +11,7 @@ from libcxx.test.format import LibcxxTestFormat from libcxx.compiler import CXXCompiler +from libcxx.clang_utils import getClangLinkDriver def loadSiteConfig(lit_config, config, param_name, env_name): @@ -343,7 +344,9 @@ def configure_compile_flags_header_includes(self): support_path = os.path.join(self.libcxx_src_root, 'test/support') self.cxx.compile_flags += ['-I' + support_path] - self.cxx.compile_flags += ['-include', os.path.join(support_path, 'nasty_macros.hpp')] + self.cxx.compile_flags += [ + '-include', os.path.join(support_path, 'nasty_macros.hpp') + ] libcxx_headers = self.get_lit_conf( 'libcxx_headers', os.path.join(self.libcxx_src_root, 'include')) if not os.path.isdir(libcxx_headers): @@ -462,8 +465,8 @@ enable_warnings = self.get_lit_bool('enable_warnings', False) if enable_warnings: self.cxx.compile_flags += ['-Wsystem-headers', '-Wall', '-Werror'] - if ('clang' in self.config.available_features or - 'apple-clang' in self.config.available_features): + if ('clang' in self.config.available_features + or 'apple-clang' in self.config.available_features): self.cxx.compile_flags += ['-Wno-user-defined-literals'] def configure_sanitizer(self): @@ -481,18 +484,30 @@ symbolizer_search_paths) # Setup the sanitizer compile flags self.cxx.flags += ['-g', '-fno-omit-frame-pointer'] + manual_link = ('darwin' in self.config.target_triple and + '-nodefaultlibs' in self.cxx.link_flags) + link_cxx = CXXCompiler(self.cxx.path, self.cxx.flags) + link_driver = getClangLinkDriver(link_cxx) + if manual_link: + self.cxx.link_flags += [ + '-Wl,-rpath,' + link_driver.getClangLibraryDir() + ] if sys.platform.startswith('linux'): self.cxx.link_flags += ['-ldl'] if san == 'Address': self.cxx.flags += ['-fsanitize=address'] if llvm_symbolizer is not None: self.env['ASAN_SYMBOLIZER_PATH'] = llvm_symbolizer + if manual_link: + self.cxx.link_flags += link_driver.getASanLinkOptions() self.config.available_features.add('asan') elif san == 'Memory' or san == 'MemoryWithOrigins': self.cxx.flags += ['-fsanitize=memory'] if san == 'MemoryWithOrigins': self.cxx.compile_flags += [ '-fsanitize-memory-track-origins'] + if manual_link: + self.cxx.link_flags += link_driver.getMSanLinkOptions() if llvm_symbolizer is not None: self.env['MSAN_SYMBOLIZER_PATH'] = llvm_symbolizer self.config.available_features.add('msan') @@ -501,9 +516,13 @@ '-fno-sanitize=vptr,function', '-fno-sanitize-recover'] self.cxx.compile_flags += ['-O3'] + if manual_link: + self.cxx.link_flags += link_driver.getUBSanLinkOptions() self.config.available_features.add('ubsan') elif san == 'Thread': self.cxx.flags += ['-fsanitize=thread'] + if manual_link: + self.cxx.link_flags += link_driver.getTSanLinkOptions() self.config.available_features.add('tsan') else: self.lit_config.fatal('unsupported value for '