-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathcc.py
309 lines (255 loc) · 10.4 KB
/
cc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
from setuptools import distutils as dutils
from setuptools.command import build_ext
from setuptools.extension import Extension
import os
import shutil
import sys
import tempfile
from numba.core import typing, sigutils
from numba.core.compiler_lock import global_compiler_lock
from numba.pycc.compiler import ModuleCompiler, ExportEntry
from numba.pycc.platform import Toolchain
from numba import cext
dir_util = dutils.dir_util
log = dutils.log
extension_libs = cext.get_extension_libs()
class CC(object):
"""
An ahead-of-time compiler to create extension modules that don't
depend on Numba.
"""
# NOTE: using ccache can speed up repetitive builds
# (especially for the mixin modules)
_mixin_sources = ['modulemixin.c',] + extension_libs
# -flto strips all unused helper functions, which 1) makes the
# produced output much smaller and 2) can make the linking step faster.
# (the Windows linker seems to do this by default, judging by the results)
_extra_cflags = {
# Comment out due to odd behavior with GCC 4.9+ with LTO
# 'posix': ['-flto'],
}
_extra_ldflags = {
# Comment out due to odd behavior with GCC 4.9+ with LTO
# 'posix': ['-flto'],
}
def __init__(self, extension_name, source_module=None):
if '.' in extension_name:
raise ValueError("basename should be a simple module name, not "
"qualified name")
self._basename = extension_name
self._init_function = 'pycc_init_' + extension_name
self._exported_functions = {}
# Resolve source module name and directory
f = sys._getframe(1)
if source_module is None:
dct = f.f_globals
source_module = dct['__name__']
elif hasattr(source_module, '__name__'):
dct = source_module.__dict__
source_module = source_module.__name__
else:
dct = sys.modules[source_module].__dict__
self._source_path = dct.get('__file__', '')
self._source_module = source_module
self._toolchain = Toolchain()
self._verbose = False
# By default, output in directory of caller module
self._output_dir = os.path.dirname(self._source_path)
self._output_file = self._toolchain.get_ext_filename(extension_name)
self._use_nrt = True
self._target_cpu = ''
@property
def name(self):
"""
The name of the extension module to create.
"""
return self._basename
@property
def output_file(self):
"""
The specific output file (a DLL) that will be generated.
"""
return self._output_file
@output_file.setter
def output_file(self, value):
self._output_file = value
@property
def output_dir(self):
"""
The directory the output file will be put in.
"""
return self._output_dir
@output_dir.setter
def output_dir(self, value):
self._output_dir = value
@property
def use_nrt(self):
return self._use_nrt
@use_nrt.setter
def use_nrt(self, value):
self._use_nrt = value
@property
def target_cpu(self):
"""
The target CPU model for code generation.
"""
return self._target_cpu
@target_cpu.setter
def target_cpu(self, value):
self._target_cpu = value
@property
def verbose(self):
"""
Whether to display detailed information when compiling.
"""
return self._verbose
@verbose.setter
def verbose(self, value):
self._verbose = value
def export(self, exported_name, sig):
"""
Mark a function for exporting in the extension module.
"""
fn_args, fn_retty = sigutils.normalize_signature(sig)
sig = typing.signature(fn_retty, *fn_args)
if exported_name in self._exported_functions:
raise KeyError("duplicated export symbol %s" % (exported_name))
def decorator(func):
entry = ExportEntry(exported_name, sig, func)
self._exported_functions[exported_name] = entry
return func
return decorator
@property
def _export_entries(self):
return sorted(self._exported_functions.values(),
key=lambda entry: entry.symbol)
def _get_mixin_sources(self):
here = os.path.dirname(__file__)
mixin_sources = self._mixin_sources[:]
if self._use_nrt:
mixin_sources.append('../core/runtime/nrt.cpp')
return [os.path.join(here, f) for f in mixin_sources]
def _get_mixin_defines(self):
# Macro definitions required by modulemixin.c
return [
('PYCC_MODULE_NAME', self._basename),
('PYCC_USE_NRT', int(self._use_nrt)),
]
def _get_extra_cflags(self):
extra_cflags = self._extra_cflags.get(sys.platform, [])
if not extra_cflags:
extra_cflags = self._extra_cflags.get(os.name, [])
return extra_cflags
def _get_extra_ldflags(self):
extra_ldflags = self._extra_ldflags.get(sys.platform, [])
if not extra_ldflags:
extra_ldflags = self._extra_ldflags.get(os.name, [])
# helperlib uses pthread on linux. make sure we are linking to it.
if sys.platform.startswith("linux"):
if "-pthread" not in extra_ldflags:
extra_ldflags.append('-pthread')
return extra_ldflags
def _compile_mixins(self, build_dir):
sources = self._get_mixin_sources()
macros = self._get_mixin_defines()
include_dirs = self._toolchain.get_python_include_dirs()
extra_cflags = self._get_extra_cflags()
# XXX distutils creates a whole subtree inside build_dir,
# e.g. /tmp/test_pycc/home/antoine/numba/numba/pycc/modulemixin.o
objects = self._toolchain.compile_objects(sources, build_dir,
include_dirs=include_dirs,
macros=macros,
extra_cflags=extra_cflags)
return objects
@global_compiler_lock
def _compile_object_files(self, build_dir):
compiler = ModuleCompiler(self._export_entries, self._basename,
self._use_nrt, cpu_name=self._target_cpu)
compiler.external_init_function = self._init_function
temp_obj = os.path.join(build_dir,
os.path.splitext(self._output_file)[0] + '.o')
log.info("generating LLVM code for '%s' into %s",
self._basename, temp_obj)
compiler.write_native_object(temp_obj, wrap=True)
return [temp_obj], compiler.dll_exports
@global_compiler_lock
def compile(self):
"""
Compile the extension module.
"""
self._toolchain.verbose = self.verbose
build_dir = tempfile.mkdtemp(prefix='pycc-build-%s-' % self._basename)
# Compile object file
objects, dll_exports = self._compile_object_files(build_dir)
# Compile mixins
objects += self._compile_mixins(build_dir)
# Then create shared library
extra_ldflags = self._get_extra_ldflags()
output_dll = os.path.join(self._output_dir, self._output_file)
libraries = self._toolchain.get_python_libraries()
library_dirs = self._toolchain.get_python_library_dirs()
self._toolchain.link_shared(output_dll, objects,
libraries, library_dirs,
export_symbols=dll_exports,
extra_ldflags=extra_ldflags)
shutil.rmtree(build_dir)
def distutils_extension(self, **kwargs):
"""
Create a distutils extension object that can be used in your
setup.py.
"""
macros = kwargs.pop('macros', []) + self._get_mixin_defines()
depends = kwargs.pop('depends', []) + [self._source_path]
extra_compile_args = (kwargs.pop('extra_compile_args', [])
+ self._get_extra_cflags())
extra_link_args = (kwargs.pop('extra_link_args', [])
+ self._get_extra_ldflags())
include_dirs = (kwargs.pop('include_dirs', [])
+ self._toolchain.get_python_include_dirs())
libraries = (kwargs.pop('libraries', [])
+ self._toolchain.get_python_libraries())
library_dirs = (kwargs.pop('library_dirs', [])
+ self._toolchain.get_python_library_dirs())
python_package_path = self._source_module[:self._source_module.rfind('.')+1]
ext = _CCExtension(name=python_package_path + self._basename,
sources=self._get_mixin_sources(),
depends=depends,
define_macros=macros,
include_dirs=include_dirs,
libraries=libraries,
library_dirs=library_dirs,
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args,
**kwargs)
ext.monkey_patch_distutils()
ext._cc = self
return ext
class _CCExtension(Extension):
"""
A Numba-specific Extension subclass to LLVM-compile pure Python code
to an extension module.
"""
_cc = None
_distutils_monkey_patched = False
def _prepare_object_files(self, build_ext):
cc = self._cc
dir_util.mkpath(os.path.join(build_ext.build_temp, *self.name.split('.')[:-1]))
objects, _ = cc._compile_object_files(build_ext.build_temp)
# Add generated object files for linking
self.extra_objects = objects
@classmethod
def monkey_patch_distutils(cls):
"""
Monkey-patch distutils with our own build_ext class knowing
about pycc-compiled extensions modules.
"""
if cls._distutils_monkey_patched:
return
_orig_build_ext = build_ext.build_ext
class _CC_build_ext(_orig_build_ext):
def build_extension(self, ext):
if isinstance(ext, _CCExtension):
ext._prepare_object_files(self)
_orig_build_ext.build_extension(self, ext)
build_ext.build_ext = _CC_build_ext
cls._distutils_monkey_patched = True