shell_compiler.cpp
1 /*
2  * This file is part of CasADi.
3  *
4  * CasADi -- A symbolic framework for dynamic optimization.
5  * Copyright (C) 2010-2023 Joel Andersson, Joris Gillis, Moritz Diehl,
6  * KU Leuven. All rights reserved.
7  * Copyright (C) 2011-2014 Greg Horn
8  *
9  * CasADi is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 3 of the License, or (at your option) any later version.
13  *
14  * CasADi is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with CasADi; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22  *
23  */
24 
25 
26 #include "shell_compiler.hpp"
27 #include "casadi/core/casadi_misc.hpp"
28 #include "casadi/core/casadi_meta.hpp"
29 #include "casadi/core/casadi_logger.hpp"
30 #include <fstream>
31 
32 // Set default object file suffix
33 #ifndef OBJECT_FILE_SUFFIX
34 #define OBJECT_FILE_SUFFIX CasadiMeta::object_file_suffix()
35 #endif // OBJECT_FILE_SUFFIX
36 
37 #include <cstdlib>
38 
39 namespace casadi {
40 
41  extern "C"
42  int CASADI_IMPORTER_SHELL_EXPORT
43  casadi_register_importer_shell(ImporterInternal::Plugin* plugin) {
44  plugin->creator = ShellCompiler::creator;
45  plugin->name = "shell";
46  plugin->doc = ShellCompiler::meta_doc.c_str();
47  plugin->version = CASADI_VERSION;
48  plugin->options = &ShellCompiler::options_;
49  return 0;
50  }
51 
52  extern "C"
53  void CASADI_IMPORTER_SHELL_EXPORT casadi_load_importer_shell() {
55  }
56 
57  ShellCompiler::ShellCompiler(const std::string& name) :
58  ImporterInternal(name) {
59  handle_ = nullptr;
60  }
61 
63  if (handle_) close_shared_library(handle_);
64 
65  if (cleanup_) {
66  if (remove(bin_name_.c_str())) casadi_warning("Failed to remove " + bin_name_);
67  if (remove(obj_name_.c_str())) casadi_warning("Failed to remove " + obj_name_);
68  for (const std::string& s : extra_suffixes_) {
69  std::string name = base_name_+s;
70  remove(name.c_str());
71  }
72  }
73  }
74 
77  {{"compiler",
78  {OT_STRING,
79  "Compiler command"}},
80  {"linker",
81  {OT_STRING,
82  "Linker command"}},
83  {"directory",
84  {OT_STRING,
85  "Directory to put temporary objects in. Must end with a file separator."}},
86  {"compiler_setup",
87  {OT_STRING,
88  "Compiler setup command. Intended to be fixed."
89  " The 'flag' option is the prefered way to set"
90  " custom flags."}},
91  {"linker_setup",
92  {OT_STRING,
93  "Linker setup command. Intended to be fixed."
94  " The 'flag' option is the prefered way to set"
95  " custom flags."}},
96  {"compiler_flags",
98  "Alias for 'compiler_flags'"}},
99  {"flags",
101  "Compile flags for the JIT compiler. Default: None"}},
102  {"linker_flags",
104  "Linker flags for the JIT compiler. Default: None"}},
105  {"cleanup",
106  {OT_BOOL,
107  "Cleanup temporary files when unloading. Default: true"}},
108  {"compiler_output_flag",
109  {OT_STRING,
110  "Compiler flag to denote object output. Default: '-o '"}},
111  {"compiler_include_flag",
112  {OT_STRING,
113  "Compiler flag to add an include directory. Default: '-I' ('/I' on MSVC)"}},
114  {"include_dirs",
116  "List of include directories, each passed to the compiler prefixed with "
117  "'compiler_include_flag'. OS-agnostic alternative to passing '-I...' via 'flags'. "
118  "Default: None"}},
119  {"linker_output_flag",
120  {OT_STRING,
121  "Linker flag to denote shared library output. Default: '-o '"}},
122  {"extra_suffixes",
124  "List of suffixes for extra files that the compiler may generate. Default: None"}},
125  {"name",
126  {OT_STRING,
127  "The file name used to write out compiled objects/libraries. "
128  "The actual file names used depend on 'temp_suffix' and include extensions. "
129  "Default: 'tmp_casadi_compiler_shell'"}},
130  {"temp_suffix",
131  {OT_BOOL,
132  "Use a temporary (seemingly random) filename suffix for file names. "
133  "This is desired for thread-safety. "
134  "This behaviour may defeat caching compiler wrappers. "
135  "Default: true"}},
136  }
137  };
138 
139  void ShellCompiler::init(const Dict& opts) {
140  // Base class
142 
143  // Default options
144 
145  cleanup_ = true;
146  bool temp_suffix = true;
147  std::string bare_name = "tmp_casadi_compiler_shell";
148  std::string directory = FunctionInternal::get_jit_directory(opts);
149 
150  std::vector<std::string> compiler_flags;
151  std::vector<std::string> linker_flags;
152  std::vector<std::string> include_dirs;
153  std::string suffix = OBJECT_FILE_SUFFIX;
154 
155 #ifdef _WIN32
156  std::string compiler = "cl.exe";
157  std::string linker = "link.exe";
158  std::string compiler_setup = "/c";
159  std::string linker_setup = "/DLL";
160  std::string compiler_output_flag = "/Fo";
161  std::string linker_output_flag = "/out:";
162  std::string compiler_include_flag = "/I";
163  extra_suffixes_ = {".exp", ".lib"};
164 #elif defined(__APPLE__)
165  std::string compiler = "clang";
166  std::string linker = "clang";
167  std::string compiler_setup = "-fPIC -c";
168  std::string linker_setup = "-shared";
169  std::string compiler_output_flag = "-o ";
170  std::string linker_output_flag = "-o ";
171  std::string compiler_include_flag = "-I";
172 #else
173  std::string compiler = "gcc";
174  std::string linker = "gcc";
175  std::string compiler_setup = "-fPIC -c";
176  std::string linker_setup = "-shared";
177  std::string compiler_output_flag = "-o ";
178  std::string linker_output_flag = "-o ";
179  std::string compiler_include_flag = "-I";
180 #endif
181 
182  // Read options
183  for (auto&& op : opts) {
184  if (op.first=="compiler") {
185  compiler = op.second.to_string();
186  } else if (op.first=="linker") {
187  linker = op.second.to_string();
188  } else if (op.first=="compiler_setup") {
189  compiler_setup = op.second.to_string();
190  } else if (op.first=="cleanup") {
191  cleanup_ = op.second;
192  } else if (op.first=="linker_setup") {
193  linker_setup = op.second.to_string();
194  } else if (op.first=="compiler_flags" || op.first=="flags") {
195  compiler_flags = op.second;
196  } else if (op.first=="linker_flags") {
197  linker_flags = op.second;
198  } else if (op.first=="compiler_output_flag") {
199  compiler_output_flag = op.second.to_string();
200  } else if (op.first=="linker_output_flag") {
201  linker_output_flag = op.second.to_string();
202  } else if (op.first=="compiler_include_flag") {
203  compiler_include_flag = op.second.to_string();
204  } else if (op.first=="include_dirs") {
205  include_dirs = op.second.to_string_vector();
206  } else if (op.first=="extra_suffixes") {
207  extra_suffixes_ = op.second.to_string_vector();
208  } else if (op.first=="name") {
209  bare_name = op.second.to_string();
210  } else if (op.first=="temp_suffix") {
211  temp_suffix = op.second;
212  }
213  }
214 
215  // Name of temporary file
216  if (temp_suffix) {
217  obj_name_ = temporary_file(bare_name, suffix, directory);
218  } else {
219  obj_name_ = directory + bare_name + suffix;
220  }
221  base_name_ = std::string(obj_name_.begin(), obj_name_.begin()+obj_name_.size()-suffix.size());
222  bin_name_ = base_name_+SHARED_LIBRARY_SUFFIX;
223 
224  // Construct the compiler command
225  std::stringstream cccmd;
226  cccmd << compiler;
227  for (auto i=compiler_flags.begin(); i!=compiler_flags.end(); ++i) {
228  cccmd << " " << *i;
229  }
230  // Include directories (OS-agnostic: prefix with the compiler's include flag)
231  for (const std::string& dir : include_dirs) {
232  cccmd << " " << compiler_include_flag << dir;
233  }
234  cccmd << " " << compiler_setup;
235 
236  // C/C++ source file
237  cccmd << " " << name_;
238 
239  // Temporary object file
240  cccmd << " " + compiler_output_flag << obj_name_;
241 
242  // Compile into an object
243  if (verbose_) casadi_message("calling \"" + cccmd.str() + "\"");
244  if (system(cccmd.str().c_str())) {
245  casadi_error("Compilation failed. Tried \"" + cccmd.str() + "\"");
246  }
247 
248  // Link step
249  std::stringstream ldcmd;
250  ldcmd << linker;
251 
252  // Temporary file
253  ldcmd << " " << obj_name_ << " " + linker_output_flag + bin_name_;
254 
255  // Add flags
256  for (auto i=linker_flags.begin(); i!=linker_flags.end(); ++i) {
257  ldcmd << " " << *i;
258  }
259  ldcmd << " " << linker_setup;
260 
261  // Compile into a shared library
262  if (verbose_) casadi_message("calling \"" + ldcmd.str() + "\"");
263  if (system(ldcmd.str().c_str())) {
264  casadi_error("Linking failed. Tried \"" + ldcmd.str() + "\"");
265  }
266 
267  std::vector<std::string> search_paths = get_search_paths();
268  handle_ = open_shared_library(bin_name_, search_paths, "ShellCompiler::init");
269 
270  }
271 
272  std::string ShellCompiler::library() const {
273  return bin_name_;
274  }
275 
276  signal_t ShellCompiler::get_function(const std::string& symname) {
277 #ifdef _WIN32
278  return (signal_t)GetProcAddress(handle_, TEXT(symname.c_str()));
279 #else // _WIN32
280  signal_t fcnPtr = reinterpret_cast<signal_t>(dlsym(handle_, symname.c_str()));
281  if (dlerror()) {
282  fcnPtr=nullptr;
283  dlerror(); // Reset error flags
284  }
285  return fcnPtr;
286 #endif // _WIN32
287  }
288 
289 } // namespace casadi
static std::string get_jit_directory(const Dict &jit_options)
Get JIT directory from options.
Importer internal class.
bool verbose_
Verbose – for debugging purposes.
virtual void init(const Dict &opts)
Initialize.
static const Options options_
Options.
std::string name_
C filename.
static void registerPlugin(const Plugin &plugin, bool needs_lock=true)
Register an integrator in the factory.
std::string bin_name_
Temporary file.
std::string obj_name_
Temporary file.
static const Options options_
Options.
static ImporterInternal * creator(const std::string &name)
Create a new JIT function.
ShellCompiler(const std::string &name)
Constructor.
std::vector< std::string > extra_suffixes_
Extra files.
static const std::string meta_doc
A documentation string.
~ShellCompiler() override
Destructor.
bool cleanup_
Cleanup temporary files when unloading.
signal_t get_function(const std::string &symname) override
Get a function pointer for numerical evaluation.
void init(const Dict &opts) override
Initialize.
std::string library() const override
Get library name.
The casadi namespace.
Definition: archiver.cpp:28
int CASADI_IMPORTER_SHELL_EXPORT casadi_register_importer_shell(ImporterInternal::Plugin *plugin)
std::vector< std::string > get_search_paths()
Definition: casadi_os.cpp:79
@ OT_STRINGVECTOR
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
void(* signal_t)(void)
Function pointer types for the C API.
bool remove(const std::string &path)
Definition: ghc.cpp:47
void CASADI_IMPORTER_SHELL_EXPORT casadi_load_importer_shell()
std::string temporary_file(const std::string &prefix, const std::string &suffix, const std::string &directory)
Options metadata for a class.
Definition: options.hpp:40