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