Hide keyboard shortcuts

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 

9 

10 

11_header_c_float = """ 

12void concat_float_float(float* xy, float x, float y) 

13{ 

14 xy[0] = x; 

15 xy[1] = y; 

16} 

17 

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} 

24 

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} 

30 

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} 

36 

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} 

42 

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} 

48 

49void sign_float(float* res, float x) 

50{ 

51 *res = x >= 0 ? (float)1 : (float)0 ; 

52} 

53 

54void atake_float_int(float* res, float * vx, int p, int dim) 

55{ 

56 *res = vx[p]; 

57} 

58 

59void atake_int_int(int* res, int* vx, int p, int dim) 

60{ 

61 *res = vx[p]; 

62} 

63 

64typedef int bool; 

65 

66""" 

67 

68_header_c_double = _header_c_float.replace("float", "double") 

69 

70 

71class CompilationError(Exception): 

72 """ 

73 Raised when a compilation error was detected. 

74 """ 

75 pass 

76 

77 

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. 

83 

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 

93 

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())))) 

107 

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 = [] 

114 

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) 

124 

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) 

143 

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) 

149 

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))) 

171 

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) 

180 

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) 

184 

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) 

200 

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 

228 

229 def wrapper_double(features, output=None): 

230 "wrapper for double" 

231 return wrapper(features, output, "double*", numpy.float64) 

232 

233 def wrapper_float(features, output=None): 

234 "wrapper for float" 

235 return wrapper( # pragma: no cover 

236 features, output, "float*", numpy.float32) 

237 

238 return wrapper_float if is_float else wrapper_double