clang_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 "clang_compiler.hpp"
27 #include "casadi/core/casadi_os.hpp"
28 #include "casadi/core/casadi_misc.hpp"
29 #include "casadi/core/casadi_meta.hpp"
30 #include "casadi/core/filesystem_impl.hpp"
31 #include <fstream>
32 
33 // To be able to get the plugin path
34 #ifdef _WIN32 // also for 64-bit
35 #define NOMINMAX
36 #include <windows.h>
37 #include <shlwapi.h>
38 #else // _WIN32
39 #include <dlfcn.h>
40 #endif // _WIN32
41 
42 namespace casadi {
43 
44  extern "C"
45  int CASADI_IMPORTER_CLANG_EXPORT
46  casadi_register_importer_clang(ImporterInternal::Plugin* plugin) {
47  plugin->creator = ClangCompiler::creator;
48  plugin->name = "clang";
49  plugin->doc = ClangCompiler::meta_doc.c_str();
50  plugin->version = CASADI_VERSION;
51  plugin->options = &ClangCompiler::options_;
52  return 0;
53  }
54 
55  extern "C"
56  void CASADI_IMPORTER_CLANG_EXPORT casadi_load_importer_clang() {
58  }
59 
60  ClangCompiler::ClangCompiler(const std::string& name) :
61  ImporterInternal(name) {
62 
63  myerr_ = nullptr;
64  executionEngine_ = nullptr;
65  context_ = nullptr;
66  act_ = nullptr;
67  }
68 
70  if (act_) delete act_; // NOLINT(readability-delete-null-pointer)
71  if (myerr_) delete myerr_; // NOLINT(readability-delete-null-pointer)
72  if (executionEngine_) delete executionEngine_; // NOLINT(readability-delete-null-pointer)
73  if (context_) delete context_; // NOLINT(readability-delete-null-pointer)
74  }
75 
78  {{"include_path",
79  {OT_STRING,
80  "Include paths for the JIT compiler. "
81  "The include directory shipped with CasADi will be automatically appended."}},
82  {"flags",
84  "Compile flags for the JIT compiler. Default: None"}}
85  }
86  };
87 
88  void ClangCompiler::init(const Dict& opts) {
89  // Base class
91 
92  // Read options
93  for (auto&& op : opts) {
94  if (op.first=="include_path") {
95  include_path_ = op.second.to_string();
96  } else if (op.first=="flags") {
97  flags_ = op.second;
98  }
99  }
100 
101  // Arguments to pass to the clang frontend
102  std::vector<const char *> args(1, name_.c_str());
103  for (auto&& f : flags_) {
104  args.push_back(f.c_str());
105  }
106 
107  // Create the compiler instance
108  clang::CompilerInstance compInst;
109 
110  // A symbol in the DLL
111  void *addr = reinterpret_cast<void*>(&casadi_register_importer_clang);
112 
113  // Get runtime include path
114  std::string jit_include;
115 #ifdef _WIN32
116  char buffer[MAX_PATH];
117  HMODULE hm = NULL;
118  if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
119  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
120  (LPCSTR)addr, &hm)) {
121  casadi_error("GetModuleHandle failed");
122  }
123  GetModuleFileNameA(hm, buffer, sizeof(buffer));
124  PathRemoveFileSpecA(buffer);
125  jit_include = buffer;
126 #else // _WIN32
127  Dl_info dl_info;
128  if (!dladdr(addr, &dl_info)) {
129  casadi_error("dladdr failed");
130  }
131  jit_include = dl_info.dli_fname;
132  jit_include = jit_include.substr(0, jit_include.find_last_of('/'));
133 #endif // _WIN32
134  jit_include += filesep() + "casadi" + filesep() + "jit";
135 
136 #if 0
137  // Initialize target info with the default triple for our platform.
138  auto targetoptions = std::make_shared<clang::Taroptions>();
139  targetoptions->Triple = llvm::sys::getDefaultTargetTriple();
140  clang::TargetInfo *targetInfo =
141  clang::TargetInfo::CreateTargetInfo(compInst.get_diagnostics(), targetoptions);
142  compInst.setTarget(targetInfo);
143 #endif
144 
145  // The compiler invocation needs a DiagnosticsEngine so it can report problems
146  clang::DiagnosticOptions* diagOpts = new clang::DiagnosticOptions();
147  myerr_ = new llvm::raw_os_ostream(uerr());
148  clang::TextDiagnosticPrinter *diagClient = new clang::TextDiagnosticPrinter(*myerr_, diagOpts);
149 
150  clang::DiagnosticIDs* diagID = new clang::DiagnosticIDs();
151  // This object takes ownerships of all three passed-in pointers
152  clang::DiagnosticsEngine diags(diagID, diagOpts, diagClient);
153 
154  // Create the compiler invocation
155  #if LLVM_VERSION_MAJOR >= 4
156  std::shared_ptr<clang::CompilerInvocation> compInv(new clang::CompilerInvocation());
157  #else
158  clang::CompilerInvocation* compInv = new clang::CompilerInvocation();
159  #endif
160  #if LLVM_VERSION_MAJOR >= 5
161  clang::CompilerInvocation::CreateFromArgs(*compInv, args, diags);
162  #else
163  clang::CompilerInvocation::CreateFromArgs(*compInv, &args[0],
164  &args[0] + args.size(), diags);
165  #endif
166  compInst.setInvocation(compInv);
167 
168  // Get ready to report problems
169  compInst.createDiagnostics();
170  if (!compInst.hasDiagnostics())
171  casadi_error("Cannot create diagnostics");
172 
173  // Set resource directory
174  std::string resourcedir = jit_include + filesep() + "clang" + filesep() + CLANG_VERSION_STRING;
175  compInst.getHeaderSearchOpts().ResourceDir = resourcedir;
176 
177  // Read the system includes (C or C++)
178  std::vector<std::pair<std::string, bool> >
179  system_include = getIncludes("system_includes.txt", jit_include);
180  for (auto i=system_include.begin(); i!=system_include.end(); ++i) {
181  compInst.getHeaderSearchOpts().AddPath(i->first,
182  clang::frontend::System, i->second, false);
183  }
184 
185  // Read the system includes (C only)
186  system_include = getIncludes("csystem_includes.txt", jit_include);
187  for (auto i=system_include.begin(); i!=system_include.end(); ++i) {
188  compInst.getHeaderSearchOpts().AddPath(i->first,
189  clang::frontend::CSystem, i->second, false);
190  }
191 
192  // Read the system includes (C++ only)
193  system_include = getIncludes("cxxsystem_includes.txt", jit_include);
194  for (auto i=system_include.begin(); i!=system_include.end(); ++i) {
195  compInst.getHeaderSearchOpts().AddPath(i->first,
196  clang::frontend::CXXSystem, i->second, false);
197  }
198 
199  // Search path
200  std::stringstream paths;
201  paths << include_path_ << pathsep();
202  std::string path;
203  while (std::getline(paths, path, pathsep())) {
204  compInst.getHeaderSearchOpts().AddPath(path, clang::frontend::System, false, false);
205  }
206 
207  // Create an LLVM context (NOTE: should use a static context instead?)
208  context_ = new llvm::LLVMContext();
209 
210  // Create an action and make the compiler instance carry it out
211  act_ = new clang::EmitLLVMOnlyAction(context_);
212  if (!compInst.ExecuteAction(*act_))
213  casadi_error("Cannot execute action");
214 
215  // Grab the module built by the EmitLLVMOnlyAction
216  #if LLVM_VERSION_MAJOR>=4 || (LLVM_VERSION_MAJOR==3 && LLVM_VERSION_MINOR>=5)
217  std::unique_ptr<llvm::Module> module = act_->takeModule();
218  module_ = module.get();
219  #else
220  llvm::Module* module = act_->takeModule();
221  module_ = module;
222  #endif
223 
224  llvm::InitializeNativeTarget();
225  llvm::InitializeNativeTargetAsmPrinter();
226 
227  // Create the JIT. This takes ownership of the module.
228  std::string ErrStr;
230  llvm::EngineBuilder(std::move(module)).setEngineKind(llvm::EngineKind::JIT)
231  .setErrorStr(&ErrStr).create();
232  if (!executionEngine_) {
233  casadi_error("Could not create ExecutionEngine: " + ErrStr);
234  }
235 
236  executionEngine_->finalizeObject();
237  }
238 
239  signal_t ClangCompiler::get_function(const std::string& symname) {
240  llvm::Function* f = module_->getFunction(symname);
241  if (f) {
242  return reinterpret_cast<signal_t>(executionEngine_->getPointerToFunction(f));
243  } else {
244  return nullptr;
245  }
246  }
247 
248  std::vector<std::pair<std::string, bool> > ClangCompiler::
249  getIncludes(const std::string& file, const std::string& path) {
250  // File separator
251 #ifdef _WIN32
252  const char sep = '\\';
253 #else // _WIN32
254  const char sep = '/';
255 #endif // _WIN32
256 
257  // Return value
258  std::vector<std::pair<std::string, bool> > ret;
259 
260  // Read line-by-line
261  auto setup_file_ptr = Filesystem::ifstream_ptr(path + sep + file);
262  std::istream& setup_file = *setup_file_ptr;
263 
264  std::string line;
265  while (std::getline(setup_file, line)) {
266  // Skip empty lines
267  if (line.empty()) continue;
268 
269  // Check if framework
270  size_t loc = line.find(" (framework directory)");
271  bool isframework = loc != std::string::npos;
272  if (isframework) {
273  // Truncate path
274  line = line.substr(0, loc);
275  }
276 
277  // Check if the path is absolute or relative
278 #ifdef _WIN32
279  bool relative = PathIsRelative(TEXT(line.c_str()));
280 #else // _WIN32
281  bool relative = line.at(0)!=sep;
282 #endif // _WIN32
283 
284  if (relative) {
285  // Relative path, make absolute
286  ret.push_back(std::make_pair(path + sep + line, isframework));
287  } else {
288  // Absolute path
289  ret.push_back(std::make_pair(line, isframework));
290  }
291  }
292 
293  return ret;
294  }
295 
296 } // namespace casadi
static std::vector< std::pair< std::string, bool > > getIncludes(const std::string &file, const std::string &path)
ClangCompiler(const std::string &name)
Constructor.
void init(const Dict &opts) override
Initialize.
clang::EmitLLVMOnlyAction * act_
~ClangCompiler() override
Destructor.
static const Options options_
Options.
static const std::string meta_doc
A documentation string.
llvm::raw_ostream * myerr_
llvm::LLVMContext * context_
std::vector< std::string > flags_
static ImporterInternal * creator(const std::string &name)
Create a new JIT function.
signal_t get_function(const std::string &symname) override
Get a function pointer for numerical evaluation.
llvm::ExecutionEngine * executionEngine_
static std::unique_ptr< std::istream > ifstream_ptr(const std::string &path, std::ios_base::openmode mode=std::ios_base::in, bool fail=true)
Definition: filesystem.cpp:135
Importer internal class.
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.
The casadi namespace.
Definition: archiver.cpp:28
std::ostream & uerr()
std::string filesep()
Definition: casadi_os.cpp:71
int CASADI_IMPORTER_CLANG_EXPORT casadi_register_importer_clang(ImporterInternal::Plugin *plugin)
@ OT_STRINGVECTOR
GenericType::Dict Dict
C++ equivalent of Python's dict or MATLAB's struct.
char pathsep()
Definition: casadi_os.cpp:64
void CASADI_IMPORTER_CLANG_EXPORT casadi_load_importer_clang()
void(* signal_t)(void)
Function pointer types for the C API.
std::vector< casadi_int > path(const std::vector< casadi_int > &map, casadi_int i_start)
Options metadata for a class.
Definition: options.hpp:40