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