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