]> www.ginac.de Git - ginac.git/blob - ginac/excompiler.cpp
Replace mktemp with mkstemp.
[ginac.git] / ginac / excompiler.cpp
1 /** @file excompiler.cpp
2  *
3  *  Functions to facilitate the conversion of a ex to a function pointer suited for
4  *  fast numerical integration.
5  *
6  */
7
8 /*
9  *  GiNaC Copyright (C) 1999-2016 Johannes Gutenberg University Mainz, Germany
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
24  */
25
26 #include "excompiler.h"
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include "ex.h"
33 #include "lst.h"
34 #include "operators.h"
35 #include "relational.h"
36 #include "symbol.h"
37
38 #ifdef HAVE_LIBDL
39 #include <dlfcn.h>
40 #endif // def HAVE_LIBDL
41 #include <unistd.h>
42 #include <stdlib.h>
43 #include <fstream>
44 #include <ios>
45 #include <sstream>
46 #include <stdexcept>
47 #include <string>
48 #include <vector>
49
50 namespace GiNaC {
51
52 #ifdef HAVE_LIBDL
53
54 /**
55  * Small class that manages modules opened by libdl. It is used by compile_ex
56  * and link_ex in order to have a clean-up of opened modules and their
57  * associated source and so-files at the time of program termination. It is
58  * supposed to be statically instantiated once (see definition of
59  * global_excompiler below). The dtor of that object then performs the
60  * clean-up. On top of that it provides some functionality shared between
61  * different compile_ex and link_ex specializations.
62  */
63 class excompiler
64 {
65         /**
66          * Holds all necessary information about opened modules.
67          */
68         struct filedesc
69         {
70                 void* module;
71                 std::string name; /**< filename with .so suffix */
72                 bool clean_up; /**< if true, source and so-file will be deleted */
73         };
74         std::vector<filedesc> filelist; /**< List of all opened modules */
75 public:
76         /**
77          * Complete clean-up of opened modules is done on destruction.
78          */
79         ~excompiler()
80         {
81                 for (auto it = filelist.begin(); it != filelist.end(); ++it) {
82                         clean_up(it);
83                 }
84         }
85         /**
86          * Adds a new module to the list.
87          */
88         void add_opened_module(void* module, const std::string& name, bool clean_up)
89         {
90                 filedesc fd;
91                 fd.module = module;
92                 fd.name = name;
93                 fd.clean_up = clean_up;
94                 filelist.push_back(fd);
95         }
96         /**
97          * Closes a module and deletes the so-file if the associated clean-up flag is true.
98          */
99         void clean_up(const std::vector<filedesc>::const_iterator it)
100         {
101                 dlclose(it->module);
102                 if (it->clean_up) {
103                         remove(it->name.c_str());
104                 }
105         }
106         /**
107          * Creates a new C source file and adds a standard header. If filename is
108          * empty, a unique random name is produced and used.
109          */
110         void create_src_file(std::string& filename, std::ofstream& ofs)
111         {
112                 if (filename.empty()) {
113                         // fill filename with unique random word
114                         const char* filename_pattern = "./GiNaCXXXXXX";
115                         char* new_filename = new char[strlen(filename_pattern)+1];
116                         strcpy(new_filename, filename_pattern);
117                         int fd = mkstemp(new_filename);
118                         if (fd == -1) {
119                                 delete[] new_filename;
120                                 throw std::runtime_error("mkstemp failed");
121                         }
122                         filename = std::string(new_filename);
123                         ofs.open(new_filename, std::ios::out);
124                         close(fd);
125                         delete[] new_filename;
126                 } else {
127                         // use parameter as filename
128                         ofs.open(filename.c_str(), std::ios::out);
129                 }
130                 
131                 if (!ofs) {
132                         throw std::runtime_error("could not create source code file for compilation");
133                 }
134
135                 ofs << "#include <stddef.h> " << std::endl;
136                 ofs << "#include <stdlib.h> " << std::endl;
137                 ofs << "#include <math.h> " << std::endl;
138                 ofs << std::endl;
139         }
140         /**
141          * Calls the shell script 'ginac-excompiler' to compile the produced C
142          * source file into an linkable so-file.  On demand the C source file is
143          * deleted.
144          */
145         void compile_src_file(const std::string filename, bool clean_up)
146         {
147                 std::string strcompile = LIBEXECDIR "ginac-excompiler " + filename;
148                 if (system(strcompile.c_str())) {
149                         throw std::runtime_error("excompiler::compile_src_file: error compiling source file!");
150                 }
151                 if (clean_up) {
152                         remove(filename.c_str());
153                 }
154         }
155         /**
156          * Links a so-file whose filename is given.
157          */
158         void* link_so_file(const std::string filename, bool clean_up)
159         {
160                 void* module = nullptr;
161                 module = dlopen(filename.c_str(), RTLD_NOW);
162                 if (module == nullptr) {
163                         throw std::runtime_error("excompiler::link_so_file: could not open compiled module!");
164                 }
165
166                 add_opened_module(module, filename, clean_up);
167
168                 return dlsym(module, "compiled_ex");
169         }
170         /**
171          * Removes a modules from the module list. Performs a clean-up before that.
172          * Every module with the given name will be affected.
173          */
174         void unlink(const std::string filename)
175         {
176                 for (auto it = filelist.begin(); it != filelist.end();) {
177                         if (it->name == filename) {
178                                 clean_up(it);
179                                 it = filelist.erase(it);
180                         } else {
181                                 ++it;
182                         }
183                 }
184         }
185 };
186
187 /**
188  * This static object manages the modules opened by the compile_ex and link_ex
189  * functions. On program termination its dtor is called and all open modules
190  * are closed. The associated source and so-files are eventually deleted then
191  * as well.
192  * In principle this could lead to a static deconstruction order fiasco, if
193  * other code from this library uses the compile_ex and link_ex functions
194  * (which it doesn't at the moment and won't in the likely future, so therefore
195  * we ignore this issue).
196  */
197 static excompiler global_excompiler;
198
199 void compile_ex(const ex& expr, const symbol& sym, FUNCP_1P& fp, const std::string filename)
200 {
201         symbol x("x");
202         ex expr_with_x = expr.subs(lst{sym==x});
203
204         std::ofstream ofs;
205         std::string unique_filename = filename;
206         global_excompiler.create_src_file(unique_filename, ofs);
207
208         ofs << "double compiled_ex(double x)" << std::endl;
209         ofs << "{" << std::endl;
210         ofs << "double res = ";
211         expr_with_x.print(GiNaC::print_csrc_double(ofs));
212         ofs << ";" << std::endl;
213         ofs << "return(res); " << std::endl;
214         ofs << "}" << std::endl;
215
216         ofs.close();
217
218         global_excompiler.compile_src_file(unique_filename, filename.empty());
219         // This is not standard compliant! ... no conversion between
220         // pointer-to-functions and pointer-to-objects ...
221         fp = (FUNCP_1P) global_excompiler.link_so_file(unique_filename+".so", filename.empty());
222 }
223
224 void compile_ex(const ex& expr, const symbol& sym1, const symbol& sym2, FUNCP_2P& fp, const std::string filename)
225 {
226         symbol x("x"), y("y");
227         ex expr_with_xy = expr.subs(lst{sym1==x, sym2==y});
228
229         std::ofstream ofs;
230         std::string unique_filename = filename;
231         global_excompiler.create_src_file(unique_filename, ofs);
232
233         ofs << "double compiled_ex(double x, double y)" << std::endl;
234         ofs << "{" << std::endl;
235         ofs << "double res = ";
236         expr_with_xy.print(GiNaC::print_csrc_double(ofs));
237         ofs << ";" << std::endl;
238         ofs << "return(res); " << std::endl;
239         ofs << "}" << std::endl;
240
241         ofs.close();
242
243         global_excompiler.compile_src_file(unique_filename, filename.empty());
244         // This is not standard compliant! ... no conversion between
245         // pointer-to-functions and pointer-to-objects ...
246         fp = (FUNCP_2P) global_excompiler.link_so_file(unique_filename+".so", filename.empty());
247 }
248
249 void compile_ex(const lst& exprs, const lst& syms, FUNCP_CUBA& fp, const std::string filename)
250 {
251         lst replacements;
252         for (std::size_t count=0; count<syms.nops(); ++count) {
253                 std::ostringstream s;
254                 s << "a[" << count << "]";
255                 replacements.append(syms.op(count) == symbol(s.str()));
256         }
257
258         std::vector<ex> expr_with_cname;
259         for (std::size_t count=0; count<exprs.nops(); ++count) {
260                 expr_with_cname.push_back(exprs.op(count).subs(replacements));
261         }
262
263         std::ofstream ofs;
264         std::string unique_filename = filename;
265         global_excompiler.create_src_file(unique_filename, ofs);
266
267         ofs << "void compiled_ex(const int* an, const double a[], const int* fn, double f[])" << std::endl;
268         ofs << "{" << std::endl;
269         for (std::size_t count=0; count<exprs.nops(); ++count) {
270                 ofs << "f[" << count << "] = ";
271                 expr_with_cname[count].print(GiNaC::print_csrc_double(ofs));
272                 ofs << ";" << std::endl;
273         }
274         ofs << "}" << std::endl;
275
276         ofs.close();
277
278         global_excompiler.compile_src_file(unique_filename, filename.empty());
279         // This is not standard compliant! ... no conversion between
280         // pointer-to-functions and pointer-to-objects ...
281         fp = (FUNCP_CUBA) global_excompiler.link_so_file(unique_filename+".so", filename.empty());
282 }
283
284 void link_ex(const std::string filename, FUNCP_1P& fp)
285 {
286         // This is not standard compliant! ... no conversion between
287         // pointer-to-functions and pointer-to-objects ...
288         fp = (FUNCP_1P) global_excompiler.link_so_file(filename, false);
289 }
290
291 void link_ex(const std::string filename, FUNCP_2P& fp)
292 {
293         // This is not standard compliant! ... no conversion between
294         // pointer-to-functions and pointer-to-objects ...
295         fp = (FUNCP_2P) global_excompiler.link_so_file(filename, false);
296 }
297
298 void link_ex(const std::string filename, FUNCP_CUBA& fp)
299 {
300         // This is not standard compliant! ... no conversion between
301         // pointer-to-functions and pointer-to-objects ...
302         fp = (FUNCP_CUBA) global_excompiler.link_so_file(filename, false);
303 }
304
305 void unlink_ex(const std::string filename)
306 {
307         global_excompiler.unlink(filename);
308 }
309
310 #else // def HAVE_LIBDL
311
312 /*
313  * In case no working libdl has been found by configure, the following function
314  * stubs preserve the interface. Every function just raises an exception.
315  */
316
317 void compile_ex(const ex& expr, const symbol& sym, FUNCP_1P& fp, const std::string filename)
318 {
319         throw std::runtime_error("compile_ex has been disabled because of missing libdl!");
320 }
321
322 void compile_ex(const ex& expr, const symbol& sym1, const symbol& sym2, FUNCP_2P& fp, const std::string filename)
323 {
324         throw std::runtime_error("compile_ex has been disabled because of missing libdl!");
325 }
326
327 void compile_ex(const lst& exprs, const lst& syms, FUNCP_CUBA& fp, const std::string filename)
328 {
329         throw std::runtime_error("compile_ex has been disabled because of missing libdl!");
330 }
331
332 void link_ex(const std::string filename, FUNCP_1P& fp)
333 {
334         throw std::runtime_error("link_ex has been disabled because of missing libdl!");
335 }
336
337 void link_ex(const std::string filename, FUNCP_2P& fp)
338 {
339         throw std::runtime_error("link_ex has been disabled because of missing libdl!");
340 }
341
342 void link_ex(const std::string filename, FUNCP_CUBA& fp)
343 {
344         throw std::runtime_error("link_ex has been disabled because of missing libdl!");
345 }
346
347 void unlink_ex(const std::string filename)
348 {
349         throw std::runtime_error("unlink_ex has been disabled because of missing libdl!");
350 }
351
352 #endif // def HAVE_LIBDL
353
354 } // namespace GiNaC