Vcsn  2.2
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  //
148  // We don't try to catch the "error:" part, because
149  // vcsn-compile loves to add colors, so "error:" is actually
150  // cluttered with ANSI escapes for colors.
151  auto is = open_input_file(err);
152  static auto r1 = std::regex{"static assertion failed: (.*)$",
153  std::regex::extended};
154  static auto r2 = std::regex{"static_assert failed \"(.*)\"$",
155  std::regex::extended};
156  std::string line;
157  std::smatch smatch;
158  std::string assertions;
159  while (std::getline(*is, line))
160  if (std::regex_search(line, smatch, r1)
161  || std::regex_search(line, smatch, r2))
162  assertions += std::string(smatch[1]) + '\n';
163  if (getenv("VCSN_VERBOSE"))
164  {
165  cmd += "\n compiler error messages:\n";
166  cmd += get_file_contents(err);
167  }
168  throw jit_error(assertions, " failed command:\n " + cmd);
169  }
170 
175  void cxx(std::string cmd, const std::string& tmp)
176  {
177  auto err = tmp + ".err";
178  if (getenv("VCSN_DEBUG"))
179  std::cerr << "run: " << cmd << std::endl;
180  if (system((cmd + " 2>'" + err + "'").c_str()))
181  throw_compiler_errors(cmd, err);
182  else
183  {
184  // At least we should see the warnings.
185  std::ifstream log{err};
186  std::cerr << log.rdbuf();
187  // If the file is empty the previous instruction sets the state
188  // of cerr to bad. We clear the error state flag to be able to
189  // read from cerr afterwards.
190  std::cerr.clear();
191  boost::filesystem::remove(err);
192  }
193  }
194 
198  void cxx_compile(const std::string& base)
199  {
200  auto tmp = tmpname(base);
201  // We try to read the error message via a regexp below. So
202  // avoid translation (we once had "erreur" instead of "error").
203  auto cmd = (std::string{"LC_ALL=C"}
204  + " " + XGETENV(VCSN_CCACHE)
205  + " " + XGETENV(VCSN_CXX)
206  + " " + XGETENV(VCSN_CXXFLAGS)
207  + " " + XGETENV(VCSN_CPPFLAGS)
208  + " -fPIC '" + base + ".cc' -c"
209  + " -o '" + tmp + ".o'");
210  cxx(cmd, tmp);
211  }
212 
216  void cxx_link(const std::string& base)
217  {
218  auto tmp = tmpname(base);
219  auto cmd = (std::string{"LC_ALL=C"}
220  + " " + XGETENV(VCSN_CXX)
221  + " " + XGETENV(VCSN_CXXFLAGS)
222  + " " + XGETENV(VCSN_LDFLAGS)
223  + " -fPIC -lvcsn '" + tmp + ".o' -shared"
224  + " -o '" + tmp + ".so'"
225  + printer_.linkflags());
226  cxx(cmd, tmp);
227  }
228 
230  std::string plugindir() const
231  {
232  auto res = xgetenv("VCSN_PLUGINDIR",
233  xgetenv("VCSN_HOME", "~/.vcsn") + "/plugins");
234  res = expand_tilda(res);
235  return res + "/";
236  }
237 
240  std::string split(const std::string& s) const
241  {
242  std::string res;
243  const size_t size = 150;
244  for (unsigned i = 0; i < s.length(); i += size)
245  {
246  if (i)
247  res += '/';
248  res += s.substr(i, size);
249  }
250  return res;
251  }
252 
261  void jit(const std::string& base)
262  {
263  auto tmp = tmpname(base);
264  {
265  namespace chr = std::chrono;
266  using clock = chr::steady_clock;
267  auto start = clock::now();
268  static bool no_python = !!getenv("VCSN_NO_PYTHON");
269  if (no_python)
270  {
271  cxx_compile(base);
272  cxx_link(base);
273  boost::filesystem::rename(tmp + ".so", base + ".so");
274  // Upon success, remove the .o file, it is large (10x
275  // compared to the *.so on erebus using clang) and not
276  // required. However the debug symbols are in there, so
277  // when debugging, leave them!
278  if (!getenv("VCSN_DEBUG"))
279  boost::filesystem::remove(tmp + ".o");
280  }
281  else
282  {
283  auto linkflags = printer_.linkflags();
284  if (!linkflags.empty())
285  linkflags = " --extra-ldflags='" + linkflags + "'";
286  cxx("vcsn compile -shared '" + base + ".cc'" + linkflags,
287  tmp);
288  }
289  auto d
290  = chr::duration_cast<chr::milliseconds>(clock::now() - start);
291  if (getenv("VCSN_TIME"))
292  {
293  std::ofstream{"/tmp/vcsn-compile.log",
294  std::ofstream::out | std::ofstream::app}
295  << d.count() << ", "
296  << (no_python ? "C++, " : "Py, ")
297  << '\'' << base.substr(plugindir().size()) << '\''
298  << '\n';
299  if (getenv("VCSN_TIME2"))
300  std::cerr << d.count() << "ms: " << base << '\n';
301  }
302  }
303  static bool first = true;
304  if (first)
305  {
306  lt_dlinit();
307  first = false;
308  }
309  lt_dlhandle lib = lt_dlopen((base + ".so").c_str());
310  VCSN_REQUIRE(lib, "cannot load lib: ", base, ".so: ", lt_dlerror());
311  }
312 
314  void compile(const std::string& ctx)
315  {
316  printer_.header("vcsn/ctx/instantiate.hh");
317  std::string base =
318  plugindir() + "contexts/" + split(ctx);
319  os << "using ctx_t =" << incendl;
320  print_context(ctx);
321  os << ';' << decendl
322  <<
323  "\n"
324  "namespace vcsn\n"
325  "{\n"
326  " VCSN_CTX_INSTANTIATE(ctx_t);\n"
327  "}\n";
328  ;
329  print(base);
330  jit(base);
331  }
332 
334  void compile(const std::set<std::pair<std::string, signature>>& algos)
335  {
336  printer_.header("vcsn/misc/attributes.hh"); // ATTRIBUTE_USED
337  printer_.header("vcsn/misc/name.hh"); // ssignature
338  printer_.header("vcsn/dyn/registries.hh");
339  for (const auto& algo: algos)
340  printer_.header_algo(algo.first);
341 
342  unsigned count = 0;
343  for (const auto& algo: algos)
344  {
345  os << iendl
346  << "// " << algo.first << '.';
347  std::string types;
348  bool first = true;
349  for (const auto& s: algo.second)
350  {
351  os << iendl;
352  std::string t = "t" + std::to_string(count) + "_t";
353  os << "using " << t << " =" << incendl;
354  print_type(s);
355  os << ';' << decendl;
356  types += (first ? "" : ", ") + t;
357  ++count;
358  first = false;
359  }
360  os <<
361  "\n"
362  "static bool " << algo.first << " ATTRIBUTE_USED =" << incendl
363  << "vcsn::dyn::detail::" << algo.first << "_register(" << incendl
364  << "vcsn::ssignature<" << types << ">()," << iendl
365  << "vcsn::dyn::detail::" << algo.first << "<" << types << ">" << decendl
366  << ");" << decendl;
367  }
368 
369  // The first algo is the once that gives its name to the
370  // file to compile.
371  std::string base = (plugindir()
372  + "algos/"
373  + begin(algos)->first + "/"
374  + split(begin(algos)->second.to_string()));
375  print(base);
376  jit(base);
377  }
378 
380  std::istringstream is;
382  std::ostringstream os;
383  ast::context_parser parser_;
384  ast::context_printer printer_;
385  };
386  } // namespace detail
387 
388  void compile(const std::string& ctx)
389  {
390  translation translate;
391  translate.compile(ctx);
392  }
393 
394  void compile(const std::string& algo, const signature& sig)
395  {
396  translation translate;
397  std::set<std::pair<std::string, signature>> algos{{algo, sig}};
398  if (algo == "delay_automaton"
399  || algo == "is_synchronized")
400  {
401  algos.emplace("delay_automaton", sig);
402  algos.emplace("is_synchronized", sig);
403  }
404  translate.compile(algos);
405  }
406  } // namespace dyn
407 } // namespace vcsn
std::shared_ptr< std::istream > open_input_file(const std::string &file)
Open file for reading and return its autoclosing stream.
Definition: stream.cc:81
std::ostream & incendl(std::ostream &o)
Increment the indentation, print an end of line, and set the indentation.
Definition: indent.cc:54
#define VCSN_REQUIRE(Cond,...)
A macro similar to require.
Definition: raise.hh:89
ast::context_parser parser_
Definition: translate.cc:383
Request the set implementation (bool weights).
Definition: a-star.hh:8
std::ostringstream os
The output stream: the corresponding C++ snippet to compile.
Definition: translate.cc:382
STL namespace.
std::ostream & decendl(std::ostream &o)
Decrement the indentation, print an end of line, and set the indentation.
Definition: indent.cc:59
jit_error(const std::string &assert, const std::string &what)
Definition: translate.cc:32
std::string type(const automaton &a)
The implementation type of a.
Definition: others.cc:206
Indentation relative functions.
std::ostream & iendl(std::ostream &o)
Print an end of line, then set the indentation.
Definition: indent.cc:49
weightset_mixin< detail::log_impl > log
Definition: fwd.hh:50
auto out(const Aut &aut, state_t_of< Aut > s)
Indexes of visible transitions leaving state s.
Definition: automaton.hh:56
ast::context_printer printer_
Definition: translate.cc:384
#define XGETENV(Name)
Definition: translate.cc:47
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:109
std::string get_file_contents(const std::string &file)
Return the contents of file.
Definition: stream.cc:66
std::istringstream is
The input stream: the specification to translate.
Definition: translate.cc:380
static dyn::context ctx(const driver &d)
Get the context of the driver.
Definition: parse.cc:82
std::string to_string(identities i)
Wrapper around operator<<.
Definition: identities.cc:17
std::ostream & print_context(const context &ctx, std::ostream &o, const std::string &fmt)
Bridge (print).
Definition: print.hh:128
polynomial split(const expression &exp)
Break exp.
Definition: split.hh:277
size_t size(const ExpSet &rs, const typename ExpSet::value_t &r)
void compile(const std::string &ctx)
Compile, and load, a DSO with instantiations for ctx.
Definition: translate.cc:388
Signature of a function call.
Definition: signature.hh:15