Vcsn  2.1
Be Rational
translate.cc
Go to the documentation of this file.
1 #include <vcsn/dyn/translate.hh>
2 
3 #include <chrono>
4 #include <fstream>
5 #include <memory>
6 #include <set>
7 #include <sstream>
8 #include <string>
9 #include <unistd.h> // getpid
10 
11 #include <boost/filesystem.hpp>
12 
13 #include <ltdl.h>
14 
17 
18 #include <vcsn/config.hh>
19 #include <vcsn/dyn/context.hh>
20 #include <vcsn/misc/escape.hh>
21 #include <vcsn/misc/indent.hh>
22 #include <vcsn/misc/raise.hh>
23 #include <vcsn/misc/regex.hh>
24 #include <vcsn/misc/signature.hh>
25 #include <vcsn/misc/stream.hh>
26 
27 namespace vcsn
28 {
29  namespace dyn
30  {
31 
32  jit_error::jit_error(const std::string& a, const std::string& what)
33  : std::runtime_error(what)
34  , assertions(a)
35  {}
36 
37  namespace
38  {
40  std::string
41  xgetenv(const std::string& var, const std::string& val = "")
42  {
43  const char* cp = getenv(var.c_str());
44  return cp ? cp : val;
45  }
46 
47 #define XGETENV(Name) xgetenv(#Name, Name)
48 
50  // http://stackoverflow.com/questions/4891006.
51  std::string expand_tilda(std::string res)
52  {
53  if (!res.empty() && res[0] == '~')
54  {
55  assert(res.size() == 1 || res[1] == '/');
56  auto home = xgetenv("HOME", xgetenv("USERPROFILE"));
57  char const *hdrive = getenv("HOMEDRIVE");
58  char const *hres = getenv("HOMERES");
59  if (!home.empty())
60  res.replace(0, 1, home);
61  else if (hdrive && hres)
62  res.replace(0, 1, std::string(hdrive) + hres);
63  else
64  res.replace(0, 1, xgetenv("VCSN_TMPDIR", "/tmp"));
65  }
66  return res;
67  }
68 
70  void ensure_parent_directory(const std::string& path)
71  {
72  boost::filesystem::path p(path);
73  boost::filesystem::create_directories(p.parent_path());
74  }
75 
77  std::string tmpname(std::string res)
78  {
79  res += ".";
80  res += std::to_string(getpid());
81  return res;
82  }
83 
84  struct translation
85  {
86  translation()
87  : parser_(is), printer_(os)
88  {}
89 
97  void print(const std::string& base)
98  {
99  ensure_parent_directory(base);
100  // For atomicity, generate a file with PID, then mv it
101  // (which is atomic on any decent OS/FS).
102  auto tmp = tmpname(base);
103  {
104  std::ofstream o{tmp + ".cc"};
105  printer_.print(o);
106  }
107  boost::filesystem::rename(tmp + ".cc", base + ".cc");
108  }
109 
112  void print_context(const std::string& ctx)
113  {
114  is.clear();
115  is.str(ctx);
116  auto ast = parser_.parse_context();
117  ast->accept(printer_);
118  }
119 
121  void print_type(const std::string& type)
122  {
123  is.clear();
124  is.str(type);
125  auto ast = parser_.parse();
126  ast->accept(printer_);
127  }
128 
132  void throw_compiler_errors(std::string cmd,
133  const std::string& err)
134  {
135  // Try to find assertion failures in the error log.
136  //
137  // $ g++-mp-4.9 -std=c++11 main.cc
138  // main.cc: In function 'int main()':
139  // main.cc:3:3: error: static assertion failed: foo
140  // static_assert(0, "foo");
141  // ^
142  // $ clang++-mp-3.5 -std=c++11 main.cc
143  // main.cc:3:3: error: static_assert failed "foo"
144  // static_assert(0, "foo");
145  // ^ ~
146  // 1 error generated.
147  auto is = open_input_file(err);
148  static std::regex r1("error: static assertion failed: (.*)$",
149  std::regex::extended);
150  static std::regex r2("error: static_assert failed \"(.*)\"$",
151  std::regex::extended);
152  std::string line;
153  std::smatch smatch;
154  std::string assertions;
155  while (std::getline(*is, line))
156  if (std::regex_search(line, smatch, r1)
157  || std::regex_search(line, smatch, r2))
158  assertions += std::string(smatch[1]) + '\n';
159  if (getenv("VCSN_VERBOSE"))
160  {
161  cmd += "\n compiler error messages:\n";
162  cmd += get_file_contents(err);
163  }
164  throw jit_error(assertions, " failed command:\n " + cmd);
165  }
166 
171  void cxx(std::string cmd, const std::string& tmp)
172  {
173  auto err = tmp + ".err";
174  if (getenv("VCSN_DEBUG"))
175  std::cerr << "run: " << cmd << std::endl;
176  if (system((cmd + " 2>'" + err + "'").c_str()))
177  throw_compiler_errors(cmd, err);
178  else
179  {
180  // At least we should see the warnings.
181  std::ifstream log{err};
182  std::cerr << log.rdbuf();
183  boost::filesystem::remove(err);
184  }
185  }
186 
190  void cxx_compile(const std::string& base)
191  {
192  auto tmp = tmpname(base);
193  // We try to read the error message via a regexp below. So
194  // avoid translation (we once had "erreur" instead of "error").
195  auto cmd = (std::string{"LC_ALL=C"}
196  + " " + XGETENV(VCSN_CCACHE)
197  + " " + XGETENV(VCSN_CXX)
198  + " " + XGETENV(VCSN_CXXFLAGS)
199  + " " + XGETENV(VCSN_CPPFLAGS)
200  + " -fPIC '" + base + ".cc' -c"
201  + " -o '" + tmp + ".o'");
202  cxx(cmd, tmp);
203  }
204 
208  void cxx_link(const std::string& base)
209  {
210  auto tmp = tmpname(base);
211  auto cmd = (std::string{"LC_ALL=C"}
212  + " " + XGETENV(VCSN_CXX)
213  + " " + XGETENV(VCSN_CXXFLAGS)
214  + " " + XGETENV(VCSN_LDFLAGS)
215  + " -fPIC -lvcsn '" + tmp + ".o' -shared"
216  + " -o '" + tmp + ".so'"
217  + printer_.linkflags());
218  cxx(cmd, tmp);
219  }
220 
222  std::string plugindir() const
223  {
224  auto res = xgetenv("VCSN_PLUGINDIR",
225  xgetenv("VCSN_HOME", "~/.vcsn") + "/plugins");
226  res = expand_tilda(res);
227  return res + "/";
228  }
229 
232  std::string split(const std::string& s) const
233  {
234  std::string res;
235  const size_t size = 200;
236  for (unsigned i = 0; i < s.length(); i += size)
237  {
238  if (i)
239  res += '/';
240  res += s.substr(i, size);
241  }
242  return res;
243  }
244 
253  void jit(const std::string& base)
254  {
255  auto tmp = tmpname(base);
256  {
257  namespace chr = std::chrono;
258  using clock = chr::steady_clock;
259  auto start = clock::now();
260  static bool no_python = !!getenv("VCSN_NO_PYTHON");
261  if (no_python)
262  {
263  cxx_compile(base);
264  cxx_link(base);
265  boost::filesystem::rename(tmp + ".so", base + ".so");
266  // Upon success, remove the .o file, it is large (10x
267  // compared to the *.so on erebus using clang) and not
268  // required. However the debug symbols are in there, so
269  // when debugging, leave them!
270  if (!getenv("VCSN_DEBUG"))
271  boost::filesystem::remove(tmp + ".o");
272  }
273  else
274  {
275  auto linkflags = printer_.linkflags();
276  if (!linkflags.empty())
277  linkflags = " --extra-ldflags='" + linkflags + "'";
278  cxx("vcsn compile -shared '" + base + ".cc'" + linkflags,
279  tmp);
280  }
281  auto d
282  = chr::duration_cast<chr::milliseconds>(clock::now() - start);
283  if (getenv("VCSN_TIME"))
284  {
285  std::ofstream{"/tmp/vcsn-compile.log",
286  std::ofstream::out | std::ofstream::app}
287  << d.count() << ", "
288  << (no_python ? "C++, " : "Py, ")
289  << '\'' << base.substr(plugindir().size()) << '\''
290  << '\n';
291  if (getenv("VCSN_TIME2"))
292  std::cerr << d.count() << "ms: " << base << '\n';
293  }
294  }
295  static bool first = true;
296  if (first)
297  {
298  lt_dlinit();
299  first = false;
300  }
301  lt_dlhandle lib = lt_dlopen((base + ".so").c_str());
302  require(lib, "cannot load lib: ", base, ".so: ", lt_dlerror());
303  }
304 
306  void compile(const std::string& ctx)
307  {
308  printer_.header("vcsn/ctx/instantiate.hh");
309  std::string base =
310  plugindir() + "contexts/" + split(ctx);
311  os << "using ctx_t =" << incendl;
312  print_context(ctx);
313  os << ';' << decendl
314  <<
315  "\n"
316  "namespace vcsn\n"
317  "{\n"
318  " VCSN_CTX_INSTANTIATE(ctx_t);\n"
319  "}\n";
320  ;
321  print(base);
322  jit(base);
323  }
324 
326  void compile(const std::set<std::pair<std::string, signature>>& algos)
327  {
328  printer_.header("vcsn/misc/attributes.hh"); // ATTRIBUTE_USED
329  printer_.header("vcsn/misc/name.hh"); // ssignature
330  printer_.header("vcsn/dyn/registries.hh");
331  for (const auto& algo: algos)
332  printer_.header_algo(algo.first);
333 
334  unsigned count = 0;
335  for (const auto& algo: algos)
336  {
337  os << iendl
338  << "// " << algo.first << '.';
339  std::string types;
340  bool first = true;
341  for (const auto& s: algo.second)
342  {
343  os << iendl;
344  std::string t = "t" + std::to_string(count) + "_t";
345  os << "using " << t << " =" << incendl;
346  print_type(s);
347  os << ';' << decendl;
348  types += (first ? "" : ", ") + t;
349  ++count;
350  first = false;
351  }
352  os <<
353  "\n"
354  "static bool " << algo.first << " ATTRIBUTE_USED =" << incendl
355  << "vcsn::dyn::detail::" << algo.first << "_register(" << incendl
356  << "vcsn::ssignature<" << types << ">()," << iendl
357  << "vcsn::dyn::detail::" << algo.first << "<" << types << ">" << decendl
358  << ");" << decendl;
359  }
360 
361  // The first algo is the once that gives its name to the
362  // file to compile.
363  std::string base = (plugindir()
364  + "algos/"
365  + begin(algos)->first + "/"
366  + split(begin(algos)->second.to_string()));
367  print(base);
368  jit(base);
369  }
370 
372  std::istringstream is;
374  std::ostringstream os;
375  ast::context_parser parser_;
376  ast::context_printer printer_;
377  };
378  } // namespace detail
379 
380  void compile(const std::string& ctx)
381  {
382  translation translate;
383  translate.compile(ctx);
384  }
385 
386  void compile(const std::string& algo, const signature& sig)
387  {
388  translation translate;
389  std::set<std::pair<std::string, signature>> algos{{algo, sig}};
390  if (algo == "delay_automaton"
391  || algo == "is_synchronized")
392  {
393  algos.emplace("delay_automaton", sig);
394  algos.emplace("is_synchronized", sig);
395  }
396  translate.compile(algos);
397  }
398  } // namespace dyn
399 } // namespace vcsn
STL namespace.
ast::context_parser parser_
Definition: translate.cc:375
#define XGETENV(Name)
Definition: translate.cc:47
std::string type(const automaton &a)
The implementation type of a.
Definition: others.cc:197
std::ostream & print(const automaton &aut, std::ostream &out, const std::string &format="default")
Print automaton a on o using format format.
Definition: print.hh:104
std::istringstream is
The input stream: the specification to translate.
Definition: translate.cc:372
std::string to_string(identities i)
Wrapper around operator<<.
Definition: identities.cc:17
ast::context_printer printer_
Definition: translate.cc:376
std::ostream & iendl(std::ostream &o)
Print an end of line, then set the indentation.
Definition: indent.cc:49
std::ostream & incendl(std::ostream &o)
Increment the indentation, print an end of line, and set the indentation.
Definition: indent.cc:54
std::shared_ptr< std::istream > open_input_file(const std::string &file)
Open file for reading and return its autoclosing stream.
Definition: stream.cc:80
jit_error(const std::string &assert, const std::string &what)
Definition: translate.cc:32
static dyn::context ctx(const driver &d)
Get the context of the driver.
Definition: parse.cc:80
void compile(const std::string &ctx)
Compile, and load, a DSO with instantiations for ctx.
Definition: translate.cc:380
polynomial split(const expression &exp)
Break exp.
Definition: split.hh:277
size_t size(const ExpSet &rs, const typename ExpSet::value_t &r)
std::ostringstream os
The output stream: the corresponding C++ snippet to compile.
Definition: translate.cc:374
void require(bool b, Args &&...args)
If b is not verified, raise an error with args as message.
Definition: raise.hh:75
Signature of a function call.
Definition: signature.hh:15
std::ostream & print_context(const context &ctx, std::ostream &o, const std::string &fmt)
Bridge (print).
Definition: print.hh:123
std::string get_file_contents(const std::string &file)
Return the contents of file.
Definition: stream.cc:65
Indentation relative functions.
weightset_mixin< detail::log_impl > log
Definition: fwd.hh:50
std::ostream & decendl(std::ostream &o)
Decrement the indentation, print an end of line, and set the indentation.
Definition: indent.cc:59