Vcsn  2.8
Be Rational
translate.cc
Go to the documentation of this file.
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/algorithm/string/case_conv.hpp>
12 #include <boost/filesystem.hpp>
13 
16 #include <lib/vcsn/dyn/type-ast.hh>
17 #include <lib/vcsn/misc/xltdl.hh>
18 
19 #include <vcsn/dyn/context.hh>
21 #include <vcsn/misc/escape.hh>
22 #include <vcsn/misc/indent.hh>
23 #include <vcsn/misc/regex.hh>
24 #include <vcsn/misc/signature.hh>
25 #include <vcsn/misc/stream.hh>
26 
27 namespace
28 {
30  int verbose = []{
31  auto res = 0;
32  auto cp = getenv("VCSN_VERBOSE");
33  std::istringstream is{cp ? cp : "0"};
34  is >> res;
35  return res;
36  }();
37 }
38 
39 namespace vcsn
40 {
41  namespace dyn
42  {
43 
44  jit_error::jit_error(const std::string& a, const std::string& what)
45  : std::runtime_error(what)
46  , assertions(a)
47  {}
48 
49  namespace
50  {
51 #define XGETENV(Name) xgetenv(#Name, Name)
52 
55  std::string
56  config(const std::string& var)
57  {
58  auto envvar = "VCSN_" + boost::algorithm::to_upper_copy(var);
59  if (auto cp = getenv(envvar.c_str()))
60  return cp;
61  else
62  return get_config()["configuration"][var].str();
63  }
64 
66  // http://stackoverflow.com/questions/4891006.
67  std::string expand_tilda(std::string res)
68  {
69  if (!res.empty() && res[0] == '~')
70  {
71  assert(res.size() == 1 || res[1] == '/');
72  auto home = xgetenv("HOME", xgetenv("USERPROFILE"));
73  char const *hdrive = getenv("HOMEDRIVE");
74  char const *hres = getenv("HOMERES");
75  if (!home.empty())
76  res.replace(0, 1, home);
77  else if (hdrive && hres)
78  res.replace(0, 1, std::string(hdrive) + hres);
79  else
80  res.replace(0, 1, xgetenv("VCSN_TMPDIR", "/tmp"));
81  }
82  return res;
83  }
84 
86  void ensure_parent_directory(const std::string& path)
87  {
88  boost::filesystem::path p(path);
89  boost::filesystem::create_directories(p.parent_path());
90  }
91 
93  std::string tmpname(std::string res)
94  {
95  res += ".";
96  res += std::to_string(getpid());
97  return res;
98  }
99 
100  struct translation
101  {
102  translation()
103  : printer_(os)
104  {}
105 
116  void print(const std::string& base)
117  {
118  ensure_parent_directory(base);
119  // For atomicity, generate a file with PID, then mv it
120  // (which is atomic on any decent OS/FS).
121  auto tmp = tmpname(base);
122  {
123  auto&& o = std::ofstream{tmp + ".cc"};
124  VCSN_REQUIRE(o.good(),
125  "cannot create ", tmp+".cc", ": ", strerror(errno));
126  printer_.print(o);
127  }
128  if (equal_files(tmp + ".cc", base + ".cc"))
129  boost::filesystem::remove(tmp + ".cc");
130  else
131  boost::filesystem::rename(tmp + ".cc", base + ".cc");
132  }
133 
136  void print_context(const std::string& ctx)
137  {
138  ast::parse_context(ctx)->accept(printer_);
139  }
140 
142  void print_type(const std::string& type)
143  {
144  ast::parse_type(type)->accept(printer_);
145  }
146 
150  void throw_compiler_errors(std::string cmd,
151  const std::string& err)
152  {
153  // Try to find assertion failures in the error log.
154  //
155  // $ g++-mp-4.9 -std=c++11 main.cc
156  // main.cc: In function 'int main()':
157  // main.cc:3:3: error: static assertion failed: foo
158  // static_assert(0, "foo");
159  // ^
160  // $ clang++-mp-3.5 -std=c++11 main.cc
161  // main.cc:3:3: error: static_assert failed "foo"
162  // static_assert(0, "foo");
163  // ^ ~
164  // 1 error generated.
165  //
166  // We don't try to catch the "error:" part, because
167  // vcsn-compile loves to add colors, so "error:" is actually
168  // cluttered with ANSI escapes for colors.
169  auto is = open_input_file(err);
170  static auto r1 = std::regex{"static assertion failed: (.*)$",
171  std::regex::extended};
172  static auto r2 = std::regex{"static_assert failed \"(.*)\"$",
173  std::regex::extended};
174  std::string line;
175  std::smatch smatch;
176  std::string assertions;
177  while (std::getline(*is, line))
178  if (std::regex_search(line, smatch, r1)
179  || std::regex_search(line, smatch, r2))
180  assertions += std::string(smatch[1]) + '\n';
181  if (verbose)
182  {
183  cmd += "\n compiler error messages:\n";
184  cmd += get_file_contents(err);
185  }
186  throw jit_error(assertions, " failed command:\n " + cmd);
187  }
188 
193  void cxx(std::string cmd, const std::string& tmp)
194  {
195  auto err = tmp + ".err";
196  if (getenv("VCSN_DEBUG"))
197  std::cerr << "run: " << cmd << '\n';
198  if (system((cmd + " 2>'" + err + "'").c_str()))
199  throw_compiler_errors(cmd, err);
200  else
201  {
202  // At least we should see the warnings.
203  auto&& log = std::ifstream{err};
205  "cannot read ", err, ": ", strerror(errno));
206  std::cerr << log.rdbuf();
207  // If the file is empty the previous instruction sets the state
208  // of cerr to bad. We clear the error state flag to be able to
209  // read from cerr afterwards.
210  std::cerr.clear();
211  boost::filesystem::remove(err);
212  }
213  }
214 
218  void cxx_compile(const std::string& base)
219  {
220  auto tmp = tmpname(base);
221  // We try to read the error message via a regexp below. So
222  // avoid translation (we once had "erreur" instead of "error").
223  auto cmd = (std::string{"LC_ALL=C"}
224  + " " + config("ccache")
225  + " " + config("cxx")
226  + " " + config("cxxflags")
227  + " " + config("cppflags")
228  + " -fPIC '" + base + ".cc' -c"
229  + " -o '" + tmp + ".o'");
230  cxx(cmd, tmp);
231  }
232 
236  void cxx_link(const std::string& base)
237  {
238  auto tmp = tmpname(base);
239  auto cmd = (std::string{"LC_ALL=C"}
240  + " " + config("cxx")
241  + " " + config("cxxflags")
242  + " " + config("ldflags")
243  + " -fPIC -lvcsn '" + tmp + ".o' -shared"
244  + " -o '" + tmp + ".so'"
245  + printer_.linkflags());
246  cxx(cmd, tmp);
247  }
248 
250  std::string plugindir() const
251  {
252  auto res = xgetenv("VCSN_PLUGINDIR",
253  xgetenv("VCSN_HOME", "~/.vcsn") + "/plugins");
254  res = expand_tilda(res);
255  return res + "/";
256  }
257 
260  std::string split(const std::string& s) const
261  {
262  auto res = std::string{};
263  const size_t size = 150;
264  for (unsigned i = 0; i < s.length(); i += size)
265  {
266  if (i)
267  res += '/';
268  res += s.substr(i, size);
269  }
270  return res;
271  }
272 
281  void jit(const std::string& base)
282  {
283  auto tmp = tmpname(base);
284  {
285  namespace chr = std::chrono;
286  using clock = chr::steady_clock;
287  auto start = clock::now();
288  static bool no_python = !!getenv("VCSN_NO_PYTHON");
289  if (no_python)
290  {
291  cxx_compile(base);
292  cxx_link(base);
293  boost::filesystem::rename(tmp + ".so", base + ".so");
294  // Upon success, remove the .o file, it is large (10x
295  // compared to the *.so on erebus using clang) and not
296  // required. However the debug symbols are in there, so
297  // when debugging, leave them!
298  if (!getenv("VCSN_DEBUG"))
299  boost::filesystem::remove(tmp + ".o");
300  }
301  else
302  {
303  auto cmd
304  = xgetenv("VCSN_COMPILE",
305  xgetenv("VCSN", "vcsn") + " compile");
306  auto linkflags = printer_.linkflags();
307  if (!linkflags.empty())
308  linkflags = " LDFLAGS+='" + linkflags + "'";
309  cxx(cmd + " -shared" + linkflags + " '" + base + ".cc'",
310  tmp);
311  }
312  auto d
313  = chr::duration_cast<chr::milliseconds>(clock::now() - start);
314  if (getenv("VCSN_TIME"))
315  {
316  std::ofstream{"/tmp/vcsn-compile.log",
317  std::ofstream::out | std::ofstream::app}
318  << d.count() << ", "
319  << (no_python ? "C++, " : "Py, ")
320  << '\'' << base.substr(plugindir().size()) << '\''
321  << '\n';
322  if (getenv("VCSN_TIME2"))
323  std::cerr << d.count() << "ms: " << base << '\n';
324  }
325  }
327  .global(true)
328  .ext()
329  .verbose(1 < verbose)
330  .open(base + ".so");
331  }
332 
334  void operator()(const std::string& ctx)
335  {
336  printer_.header("vcsn/ctx/instantiate.hh");
337  auto base = plugindir() + "contexts/" + split(ctx);
338  os << "using ctx_t =" << incendl;
339  print_context(ctx);
340  os << ';' << decendl
341  <<
342  "\n"
343  "namespace vcsn\n"
344  "{\n"
345  " VCSN_CTX_INSTANTIATE(ctx_t);\n"
346  "}\n";
347  ;
348  print(base);
349  jit(base);
350  }
351 
353  void
354  operator()(const std::set<std::pair<std::string, signature>>& algos)
355  {
356  printer_.header("vcsn/misc/attributes.hh"); // ATTRIBUTE_USED
357  printer_.header("vcsn/dyn/name.hh"); // ssignature
358  printer_.header("vcsn/dyn/registries.hh");
359  for (const auto& algo: algos)
360  printer_.header_algo(algo.first);
361 
362  unsigned count = 0;
363  for (const auto& algo: algos)
364  {
365  os << iendl
366  << "// " << algo.first << '.';
367  std::string types;
368  bool first = true;
369  for (const auto& s: algo.second)
370  {
371  os << iendl;
372  std::string t = "t" + std::to_string(count) + "_t";
373  os << "using " << t << " =" << incendl;
374  print_type(s);
375  os << ';' << decendl;
376  types += (first ? "" : ", ") + t;
377  ++count;
378  first = false;
379  }
380  os <<
381  "\n"
382  "static bool vcsn_" << algo.first << " ATTRIBUTE_USED ="
383  << incendl
384  << "vcsn::dyn::detail::" << algo.first << "_register("
385  << incendl
386  << "vcsn::ssignature<" << types << ">(),"
387  << iendl
388  << "vcsn::dyn::detail::" << algo.first << "<" << types << ">"
389  << decendl
390  << ");" << decendl;
391  }
392 
393  // The first algo is the once that gives its name to the
394  // file to compile.
395  auto base = (plugindir()
396  + "algos/"
397  + begin(algos)->first + "/"
398  + split(begin(algos)->second.to_string()));
399  print(base);
400  jit(base);
401  }
402 
404  std::ostringstream os;
405  ast::context_printer printer_;
406  };
407  } // namespace detail
408 
409  void compile(const std::string& ctx)
410  {
411  try
412  {
413  auto translate = translation{};
414  translate(ctx);
415  }
416  catch (const std::runtime_error& e)
417  {
418  raise(e, " while compiling context ", ctx);
419  }
420  }
421 
422  void compile(const std::string& algo, const signature& sig)
423  {
424  auto algos = std::set<std::pair<std::string, signature>>{{algo, sig}};
425  if (algo == "delay_automaton"
426  || algo == "is_synchronized")
427  {
428  algos.emplace("delay_automaton", sig);
429  algos.emplace("is_synchronized", sig);
430  }
431  try
432  {
433  auto translate = translation{};
434  translate(algos);
435  }
436  catch (const std::runtime_error& e)
437  {
438  raise(e, " while compiling ", algo, " for ", sig);
439  }
440  }
441  } // namespace dyn
442 } // namespace vcsn
std::ostream & iendl(std::ostream &o)
Print an end of line, then set the indentation.
Definition: indent.cc:49
polynomial split(const expression &exp)
Break exp.
size_t size(const ExpSet &rs, const typename ExpSet::value_t &r)
std::string xgetenv(const std::string &var, const std::string &val="")
getenv(var) if defined, otherwise val.
Definition: stream.cc:229
xlt_advise & global(bool global)
Definition: xltdl.cc:46
Request the set implementation (bool weights).
xlt_advise & ext()
Definition: xltdl.cc:55
std::shared_ptr< ast_node > parse_type(const std::string &type)
Parse a type, and return its AST.
std::string get_file_contents(const std::string &file)
Return the contents of file.
Definition: stream.cc:178
void compile(const std::string &ctx)
Compile, and load, a DSO with instantiations for ctx.
Definition: translate.cc:409
std::string expand_tilda(const std::string &res)
Expand initial "~" in res.
Definition: stream.cc:58
bool equal_files(const std::string &fn1, const std::string &fn2)
Whether two files have exactly equal contents.
Definition: stream.cc:39
std::shared_ptr< ast_node > parse_context(const std::string &ctx)
Parse a context, and return its AST.
std::ostringstream os
The output stream: the corresponding C++ snippet to compile.
Definition: translate.cc:404
Definition: a-star.hh:8
std::shared_ptr< std::istream > open_input_file(const std::string &file)
Open file for reading and return its autoclosing stream.
Definition: stream.cc:193
std::ostream & print_context(const context &ctx, std::ostream &o, const std::string &fmt)
Bridge (print).
Definition: print.hh:140
std::ostream & incendl(std::ostream &o)
Increment the indentation, print an end of line, and set the indentation.
Definition: indent.cc:54
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
std::string type(const automaton &a)
The implementation type of a.
Definition: others.cc:238
jit_error(const std::string &assert, const std::string &what)
Definition: translate.cc:44
ast::context_printer printer_
Definition: translate.cc:405
std::string to_string(identities i)
Wrapper around operator<<.
Definition: identities.cc:38
Signature of a function call.
Definition: signature.hh:15
detail::config & get_config()
Get the configuration singleton.
std::ostream & print(const automaton &aut, std::ostream &out=std::cout, const std::string &format="default")
Print automaton a on out using format format.
Definition: print.hh:121
xlt_handle open(const std::string &s)
Definition: xltdl.cc:105
STL namespace.
std::string assertions
If defined, static assertions that failed (ends with a eol).
Definition: translate.hh:17
Indentation relative functions.
#define VCSN_REQUIRE(Cond,...)
A macro similar to require.
Definition: raise.hh:98
return res
Definition: multiply.hh:399
xlt_advise & verbose(int v)
Whether to report dlopen attempts.
Definition: xltdl.cc:63
auto out(const Aut &aut, state_t_of< Aut > s)
Indexes of visible transitions leaving state s.
Definition: automaton.hh:86