Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2@file
3@brief Helpers to compile C.
4"""
5import os
6import sys
7import shutil
8import numpy
11_header_c_float = """
12void concat_float_float(float* xy, float x, float y)
13{
14 xy[0] = x;
15 xy[1] = y;
16}
18void adot_float_float(float* res, float* vx, float* vy, int dim)
19{
20 *res = 0;
21 for(; dim > 0; --dim, ++vx, ++vy)
22 *res += *vx * *vy;
23}
25void aadd_float(float* res, float* vx, float* vy, int dim)
26{
27 for(; dim > 0; --dim, ++vx, ++vy, ++res)
28 *res = *vx + *vy;
29}
31void asub_float_float(float* res, float* vx, float* vy, int dim)
32{
33 for(; dim > 0; --dim, ++vx, ++vy, ++res)
34 *res = *vx - *vy;
35}
37void amul_float_float(float* res, float* vx, float* vy, int dim)
38{
39 for(; dim > 0; --dim, ++vx, ++vy, ++res)
40 *res = *vx * *vy;
41}
43void adiv_float_float(float* res, float* vx, float* vy, int dim)
44{
45 for(; dim > 0; --dim, ++vx, ++vy, ++res)
46 *res = *vx / *vy;
47}
49void sign_float(float* res, float x)
50{
51 *res = x >= 0 ? (float)1 : (float)0 ;
52}
54void atake_float_int(float* res, float * vx, int p, int dim)
55{
56 *res = vx[p];
57}
59void atake_int_int(int* res, int* vx, int p, int dim)
60{
61 *res = vx[p];
62}
64typedef int bool;
66"""
68_header_c_double = _header_c_float.replace("float", "double")
71class CompilationError(Exception):
72 """
73 Raised when a compilation error was detected.
74 """
75 pass
78def compile_c_function(code_c, nbout, dtype=numpy.float32, add_header=True,
79 suffix="", additional_paths=None, tmpdir='.', fLOG=None):
80 """
81 Compiles a C function with :epkg:`cffi`.
82 It takes one features vector.
84 @param nbout number of expected outputs
85 @param code_c code C
86 @param dtype numeric type to use
87 @param add_header add common function before compiling
88 @param suffix avoid avoid the same compiled module name
89 @param additional_paths additional paths to add to the module
90 @param tmpdir see below
91 @param fLOG logging function
92 @return compiled function
94 The function assumes the first line is the signature.
95 If you are using Windows with Visual Studio 2017, make sure
96 you are using :epkg:`Python` 3.6.3+
97 (see `Issue 30389 <https://bugs.python.org/issue30389>`_).
98 Parameter *tmpdir* is used by function `compile
99 <http://cffi.readthedocs.io/en/latest/cdef.html?
100 highlight=compile#ffibuilder-compile-etc-compiling-out-of-line-modules>`_.
101 """
102 if sys.platform.startswith("win"):
103 if "VS140COMNTOOLS" not in os.environ: # pragma: no cover
104 raise CompilationError(
105 "Visual Studio is not installed.\n{0}".format(
106 "\n".join("{0}={1}".format(k, v) for k, v in sorted(os.environ.items()))))
108 sig = code_c.split("\n")[0].strip() + ";"
109 name = sig.split()[1]
110 include_paths = []
111 lib_paths = []
112 if additional_paths is None:
113 additional_paths = []
115 # ~ if len(additional_paths) == 0 and sys.platform.startswith("win") and \
116 # ~ 'VSSDK140Install' not in os.environ: # last condition is for the installed VisualStudio.
117 # ~ if fLOG:
118 #~ fLOG("[compile_c_function] fix PATH for VS2017 on Windows")
119 # ~ # Update environment variables.
120 # ~ adds = [r"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64",
121 # ~ r"C:\Program Files (x86)\Windows Kits\10\bin\10.0.15063.0\x64"]
122 # ~ vcvars64 = os.path.join(adds[0], 'vcvars64.bat')
123 #~ subprocess.run(vcvars64)
125 # ~ # Add paths for VS2017.
126 # ~ includes = [r'C:\Program Files (x86)\Windows Kits\10\Include\10.0.15063.0\shared',
127 #~ r'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include',
128 # ~ r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\SDK\ScopeCppSDK\SDK\include\ucrt']
129 # ~ libs = [r'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\lib\amd64',
130 #~ r'C:\Program Files (x86)\Windows Kits\10\Lib\10.0.15063.0\um\x64',
131 # ~ r'C:\Program Files (x86)\Windows Kits\10\Lib\10.0.15063.0\ucrt\x64']
132 # ~ opaths = os.environ['PATH'].split(';')
133 # ~ for add in adds:
134 # ~ if os.path.exists(add) and add not in opaths:
135 #~ additional_paths.append(add)
136 # ~ oinc = os.environ.get('INCLUDE', '').split(';')
137 # ~ for inc in includes:
138 # ~ if os.path.exists(inc) and inc not in oinc:
139 #~ include_paths.append(inc)
140 # ~ for lib in libs:
141 # ~ if os.path.exists(lib):
142 #~ lib_paths.append(lib)
144 if additional_paths:
145 if fLOG: # pragma: no cover
146 for p in additional_paths:
147 fLOG("[compile_c_function] PATH += '{0}'".format(p))
148 os.environ["PATH"] += ";" + ";".join(additional_paths)
150 if lib_paths and sys.platform.startswith("win"): # pragma: no cover
151 libs = ['msvcrt.lib', 'oldnames.lib', 'kernel32.lib', 'vcruntime.lib',
152 'ucrt.lib']
153 libs = {k: False for k in libs}
154 for lib in lib_paths:
155 for name in list(libs):
156 if libs[name]:
157 continue
158 msv = os.path.join(lib, name)
159 if os.path.exists(msv):
160 dst = os.getcwd()
161 msvd = os.path.join(dst, name)
162 if not os.path.exists(msvd):
163 shutil.copy(msv, dst)
164 if fLOG:
165 fLOG("[compile_c_function] copy '{0}'".format(msv))
166 libs[name] = True
167 copied = len([k for k, v in libs.items() if v])
168 if copied < len(libs):
169 raise CompilationError('Unable to find those libraries ({0}<{1}) {2} in\n{3}'.format(
170 copied, len(libs), ','.join(sorted(libs)), '\n'.join(lib_paths)))
172 if include_paths:
173 if fLOG: # pragma: no cover
174 for p in include_paths:
175 fLOG("[compile_c_function] INCLUDE += '{0}'".format(p))
176 if 'INCLUDE' in os.environ: # pragma: no cover
177 os.environ["INCLUDE"] += ";" + ";".join(include_paths)
178 else: # pragma: no cover
179 os.environ["INCLUDE"] = ";".join(include_paths)
181 is_float = dtype == numpy.float32
182 header = _header_c_float if is_float else _header_c_double
183 code = code_c if not add_header else (header + code_c)
185 from cffi import FFI
186 ffibuilder = FFI()
187 try:
188 ffibuilder.cdef(sig)
189 except Exception as e: # pragma: no cover
190 raise CompilationError(
191 "Signature is wrong\n{0}\ndue to\n{1}".format(sig, e)) from e
192 ffibuilder.set_source("_" + name + suffix, code)
193 try:
194 ffibuilder.compile(verbose=False, tmpdir=tmpdir)
195 except Exception as e: # pragma: no cover
196 raise CompilationError(
197 "Compilation failed \n{0}\ndue to\n{1}".format(sig, e)) from e
198 mod = __import__("_{0}{1}".format(name, suffix))
199 fct = getattr(mod.lib, name)
201 def wrapper(features, output, cast_type, dtype):
202 "wrapper for a vector of features"
203 if len(features.shape) != 1:
204 raise TypeError( # pragma: no cover
205 "Only one dimension for the features not {0}.".format(
206 features.shape))
207 if output is None:
208 output = numpy.zeros((nbout,), dtype=dtype)
209 else:
210 if len(output.shape) != 1:
211 raise TypeError( # pragma: no cover
212 "Only one dimension for the output not {0}.".format(
213 output.shape))
214 if output.shape[0] != nbout:
215 raise TypeError( # pragma: no cover
216 "Dimension mismatch {0} != {1} (expected).".format(
217 output.shape, nbout))
218 if output.dtype != dtype:
219 raise TypeError( # pragma: no cover
220 "Type mismatch {0} != {1} (expected).".format(
221 output.dtype, dtype))
222 ptr = features.__array_interface__['data'][0]
223 cptr = mod.ffi.cast(cast_type, ptr)
224 optr = output.__array_interface__['data'][0]
225 cout = mod.ffi.cast(cast_type, optr)
226 fct(cout, cptr)
227 return output
229 def wrapper_double(features, output=None):
230 "wrapper for double"
231 return wrapper(features, output, "double*", numpy.float64)
233 def wrapper_float(features, output=None):
234 "wrapper for float"
235 return wrapper( # pragma: no cover
236 features, output, "float*", numpy.float32)
238 return wrapper_float if is_float else wrapper_double