__init__.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. """OpenMP wrapper using a libgomp dynamically loaded library."""
  2. from ctypes.util import find_library
  3. from subprocess import check_output, CalledProcessError, DEVNULL
  4. import ctypes
  5. import os
  6. import sys
  7. import sysconfig
  8. try:
  9. # there may be an environ modification when loading config
  10. from pythran.config import compiler
  11. except ImportError:
  12. def compiler():
  13. return os.environ.get('CXX', 'c++')
  14. cxx = compiler()
  15. # This function and the `msvc_runtime_*` ones below are taken over from
  16. # numpy.distutils
  17. def get_shared_lib_extension(is_python_ext=False):
  18. """Return the correct file extension for shared libraries.
  19. Parameters
  20. ----------
  21. is_python_ext : bool, optional
  22. Whether the shared library is a Python extension. Default is False.
  23. Returns
  24. -------
  25. so_ext : str
  26. The shared library extension.
  27. Notes
  28. -----
  29. For Python shared libs, `so_ext` will typically be '.so' on Linux and OS X,
  30. and '.pyd' on Windows. For Python >= 3.2 `so_ext` has a tag prepended on
  31. POSIX systems according to PEP 3149.
  32. """
  33. confvars = sysconfig.get_config_vars()
  34. so_ext = confvars.get('EXT_SUFFIX', '')
  35. if not is_python_ext:
  36. # hardcode some known values to avoid some old distutils bugs
  37. if (sys.platform.startswith('linux') or
  38. sys.platform.startswith('gnukfreebsd')):
  39. so_ext = '.so'
  40. elif sys.platform.startswith('darwin'):
  41. so_ext = '.dylib'
  42. elif sys.platform.startswith('win'):
  43. so_ext = '.dll'
  44. else:
  45. # don't use long extension (e.g., .cpython-310-x64_64-linux-gnu.so',
  46. # see PEP 3149), but subtract that Python-specific part here
  47. so_ext = so_ext.replace('.' + confvars.get('SOABI'), '', 1)
  48. return so_ext
  49. def msvc_runtime_version():
  50. "Return version of MSVC runtime library, as defined by __MSC_VER__ macro"
  51. msc_pos = sys.version.find('MSC v.')
  52. if msc_pos != -1:
  53. msc_ver = int(sys.version[msc_pos+6:msc_pos+10])
  54. else:
  55. msc_ver = None
  56. return msc_ver
  57. def msvc_runtime_major():
  58. "Return major version of MSVC runtime coded like get_build_msvc_version"
  59. major = {1300: 70, # MSVC 7.0
  60. 1310: 71, # MSVC 7.1
  61. 1400: 80, # MSVC 8
  62. 1500: 90, # MSVC 9 (aka 2008)
  63. 1600: 100, # MSVC 10 (aka 2010)
  64. 1900: 140, # MSVC 14 (aka 2015)
  65. 1910: 141, # MSVC 141 (aka 2017)
  66. 1920: 142, # MSVC 142 (aka 2019)
  67. }.get(msvc_runtime_version(), None)
  68. return major
  69. class OpenMP(object):
  70. """
  71. Internal representation of the OpenMP module.
  72. Custom class is used to dynamically add omp runtime function
  73. to this library when function is called.
  74. """
  75. def __init__(self):
  76. # FIXME: this is broken, for at least two reasons:
  77. # (1) checking how Python was built is not a good way to determine if
  78. # MSVC is being used right now,
  79. # (2) the `msvc_runtime_major` function above came from
  80. # `numpy.distutils`, where it did not have entries for 1910/1920
  81. # and returned None instead
  82. ver = msvc_runtime_major()
  83. if ver is None:
  84. self.init_not_msvc()
  85. else:
  86. self.init_msvc(ver)
  87. def init_msvc(self, ver):
  88. vcomp_path = find_library('vcomp%d.dll' % ver)
  89. if not vcomp_path:
  90. raise ImportError("I can't find a shared library for vcomp.")
  91. else:
  92. # Load the library (shouldn't fail with an absolute path right?)
  93. self.libomp = ctypes.CDLL(vcomp_path)
  94. self.version = 20
  95. def get_libomp_names(self):
  96. """Return list of OpenMP libraries to try"""
  97. return ['omp', 'gomp', 'iomp5']
  98. def init_not_msvc(self):
  99. """ Find OpenMP library and try to load if using ctype interface. """
  100. # find_library() does not automatically search LD_LIBRARY_PATH
  101. # until Python 3.6+, so we explicitly add it.
  102. # LD_LIBRARY_PATH is used on Linux, while macOS uses DYLD_LIBRARY_PATH
  103. # and DYLD_FALLBACK_LIBRARY_PATH.
  104. env_vars = []
  105. if sys.platform == 'darwin':
  106. env_vars = ['DYLD_LIBRARY_PATH', 'DYLD_FALLBACK_LIBRARY_PATH']
  107. else:
  108. env_vars = ['LD_LIBRARY_PATH']
  109. paths = []
  110. for env_var in env_vars:
  111. env_paths = os.environ.get(env_var, '')
  112. if env_paths:
  113. paths.extend(env_paths.split(os.pathsep))
  114. libomp_names = self.get_libomp_names()
  115. if cxx is not None:
  116. for libomp_name in libomp_names:
  117. cmd = [cxx,
  118. '-print-file-name=lib{}{}'.format(
  119. libomp_name,
  120. get_shared_lib_extension())]
  121. # The subprocess can fail in various ways, including because it
  122. # doesn't support '-print-file-name'. In that case just give up.
  123. try:
  124. output = check_output(cmd,
  125. stderr=DEVNULL)
  126. path = os.path.dirname(output.decode().strip())
  127. if path:
  128. paths.append(path)
  129. except (OSError, CalledProcessError):
  130. pass
  131. for libomp_name in libomp_names:
  132. # Try to load find libomp shared library using loader search dirs
  133. libomp_path = find_library(libomp_name)
  134. # Try to use custom paths if lookup failed
  135. if not libomp_path:
  136. for path in paths:
  137. candidate_path = os.path.join(
  138. path,
  139. 'lib{}{}'.format(libomp_name,
  140. get_shared_lib_extension()))
  141. if os.path.isfile(candidate_path):
  142. libomp_path = candidate_path
  143. break
  144. # Load the library
  145. if libomp_path:
  146. try:
  147. self.libomp = ctypes.CDLL(libomp_path)
  148. except OSError:
  149. raise ImportError("found openMP library '{}' but couldn't load it. "
  150. "This may happen if you are cross-compiling.".format(libomp_path))
  151. self.version = 45
  152. return
  153. raise ImportError("I can't find a shared library for libomp, you may need to install it "
  154. "or adjust the {} environment variable.".format(env_vars[0]))
  155. def __getattr__(self, name):
  156. """
  157. Get correct function name from libgomp ready to be use.
  158. __getattr__ is call only `name != libomp` as libomp is a real
  159. attribute.
  160. """
  161. if name == 'VERSION':
  162. return self.version
  163. return getattr(self.libomp, 'omp_' + name)
  164. # see http://mail.python.org/pipermail/python-ideas/2012-May/014969.html
  165. sys.modules[__name__] = OpenMP()