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